{"id":33988443,"url":"https://github.com/hasinhayder/tyro","last_synced_at":"2025-12-13T05:56:52.048Z","repository":{"id":325566513,"uuid":"1101690081","full_name":"hasinhayder/tyro","owner":"hasinhayder","description":"Tyro is a powerful Authentication, Authorization, Role \u0026 Privilege Management solution for Laravel 12. Think of it as a Swiss Army knife that handles everything from user authentication and role-based access control to user suspension workflows.","archived":false,"fork":false,"pushed_at":"2025-12-01T09:30:30.000Z","size":293,"stargazers_count":308,"open_issues_count":1,"forks_count":19,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-12-01T13:02:47.032Z","etag":null,"topics":["api","artisan-command","authentication","authorization","laravel","php","roles-management","roles-permission-management","sanctum"],"latest_commit_sha":null,"homepage":"https://hasinhayder.github.io/tyro/","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hasinhayder.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-22T04:02:58.000Z","updated_at":"2025-12-01T13:01:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hasinhayder/tyro","commit_stats":null,"previous_names":["hasinhayder/tyro"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/hasinhayder/tyro","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasinhayder%2Ftyro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasinhayder%2Ftyro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasinhayder%2Ftyro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasinhayder%2Ftyro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hasinhayder","download_url":"https://codeload.github.com/hasinhayder/tyro/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hasinhayder%2Ftyro/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27701233,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-13T02:00:09.769Z","response_time":147,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","artisan-command","authentication","authorization","laravel","php","roles-management","roles-permission-management","sanctum"],"created_at":"2025-12-13T05:56:51.458Z","updated_at":"2025-12-13T05:56:52.040Z","avatar_url":"https://github.com/hasinhayder.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tyro\n\n**Tyro** is the a very powerful Authentication, Authorization, and Role \u0026 Privilege Management solution for Laravel 12. Think of it as a Swiss Army knife that handles everything from user authentication and role-based access control to user suspension workflows—whether you're building an API, a traditional web application, or both. With Sanctum integration, 40+ powerful CLI commands, Blade directives, and ready-made middleware, Tyro saves you weeks of development time.\n\n## Why Tyro?\n\nTyro is the complete auth and access control toolkit that works everywhere in your Laravel application:\n\n-   **Complete Authentication \u0026 Authorization.** Out-of-the-box user authentication with Sanctum, role-based access control, fine-grained privilege management, and Laravel Gate integration. Works seamlessly for APIs, web apps, and hybrid applications.\n-   **Powerful Role \u0026 Privilege System.** Create unlimited roles with granular privileges. Check permissions in controllers, middleware, Blade templates, or anywhere in your code with intuitive helpers like `$user-\u003ehasRole()`, `$user-\u003ecan()`, and `$user-\u003ehasPrivileges()`.\n-   **40+ Artisan Commands.** Manage users, roles, privileges, and tokens entirely from the CLI. Seed data, suspend users, rotate tokens, audit permissions—all without touching the database directly. Perfect for automation, CI/CD, and incident response.\n-   **Blade Directives for Views.** Use `@hasrole`, `@hasprivilege`, `@hasanyrole`, and more to conditionally render content based on user permissions. Clean, readable templates without PHP logic clutter.\n-   **User Suspension Workflows.** Freeze accounts instantly with optional reasons, automatically revoke all active tokens, and manage suspensions via CLI or REST endpoints.\n-   **Optional API Surface.** Need REST endpoints? Tyro ships production-ready routes for login, registration, user management, role CRUD, and privilege management. Don't need them? Disable with one config flag.\n-   **Security Hardened.** Sanctum tokens automatically include role and privilege abilities, suspension workflows revoke tokens instantly, and protected role slugs prevent accidental deletion.\n-   **Zero Lock-in.** Publish config, migrations, and factories to customize everything. Disable CLI commands or API routes per environment. Tyro adapts to your architecture, not the other way around.\n\n## Requirements\n\n-   PHP ^8.2\n-   Laravel ^12.0\n-   Laravel Sanctum ^4.0\n\n## Quick start (TL;DR)\n\n1. `composer require hasinhayder/tyro`\n2. `php artisan tyro:install` (sets up Sanctum, runs migrations, seeds roles/privileges, and prepares your User model)\n\nThat's it! You now have a complete authentication and authorization system. The rest of this document shows how to use Tyro's features in your application.\n\n## Step-by-step installation\n\n### 1. Install the package\n\n```bash\ncomposer require hasinhayder/tyro\n```\n\nTyro's service provider is auto-discovered. Publish its assets if you want to customize them:\n\n```bash\nphp artisan vendor:publish --tag=tyro-config\nphp artisan vendor:publish --tag=tyro-migrations\nphp artisan vendor:publish --tag=tyro-database\nphp artisan tyro:publish-config --force\nphp artisan tyro:publish-migrations --force\n```\n\nNeed the ready-made API client collection? Run `php artisan tyro:postman-collection --no-open` to print the GitHub URL for the official Postman collection, or omit `--no-open` to open it directly.\n\n### 2. Run `tyro:install` (recommended)\n\n```bash\nphp artisan tyro:install\n```\n\n`tyro:install` is the one command you need to bootstrap Tyro on a fresh project. Under the hood it:\n\n1. Calls Laravel 12's `install:api` so Sanctum's config, migration, and middleware stack are registered.\n2. Runs `php artisan migrate` (respecting `--force` when you provide it) to apply both Laravel's and Tyro's database tables.\n3. Prompts to execute `tyro:seed --force`, inserting the default role/privilege catalog plus the bootstrap admin account.\n4. Offers to run `tyro:prepare-user-model` immediately if you skip seeding so the correct traits and imports land on your user model.\n\nSkipping `tyro:install` means you must run each of those commands manually (`install:api`, `migrate`, `tyro:seed`, `tyro:prepare-user-model`). Most teams never need to—`tyro:install` keeps the happy path automated and idempotent.\n\n### 3. Run Tyro's migrations \u0026 seeders manually (optional)\n\n```bash\nphp artisan migrate\n# or, interactively\nphp artisan tyro:seed\n```\n\n\u003e ℹ️ Seeding is technically optional, but highly recommended the first time you install Tyro. `TyroSeeder` inserts the default role catalogue (Administrator, User, Customer, Editor, All, Super Admin) and creates a ready-to-use `admin@tyro.project` superuser (password `tyro`). Skipping the seeder means you'll need to create equivalent roles and an admin account manually before any ability-gated routes will authorize.\n\n### 4. Prepare your user model\n\nTyro augments whatever model you mark as `tyro.models.user` (defaults to `App\\Models\\User`). Run the following command to add the required traits:\n\n```bash\nphp artisan tyro:prepare-user-model\n```\n\nThe command above injects the required imports and trait usage automatically. Prefer editing manually? Here is what the class should look like:\n\n```php\n\u003c?php\n\nnamespace App\\Models;\n\nuse Illuminate\\Foundation\\Auth\\User as Authenticatable;\nuse Laravel\\Sanctum\\HasApiTokens;\nuse HasinHayder\\Tyro\\Concerns\\HasTyroRoles;\n\nclass User extends Authenticatable\n{\n    use HasApiTokens, HasTyroRoles;\n}\n```\n\nThat is the only code change you need. The `HasTyroRoles` trait gives your User model powerful methods for checking roles, privileges, and managing suspensions. Tyro will automatically attach the default role (slug `user`) to future registrations.\n\n## Using Tyro in Your Application\n\nTyro works everywhere in your Laravel application—controllers, middleware, Blade templates, jobs, policies, and more. Here's how to leverage its features:\n\n### Checking Roles in Code\n\n```php\n// In a controller, service, or anywhere you have the user\n$user = auth()-\u003euser();\n\n// Check single role\nif ($user-\u003ehasRole('admin')) {\n    // User is an admin\n}\n\n// Check multiple roles (user must have ALL)\nif ($user-\u003ehasRoles(['admin', 'super-admin'])) {\n    // User has both roles\n}\n\n// Get all role slugs\n$roles = $user-\u003etyroRoleSlugs(); // ['admin', 'editor']\n```\n\n### Checking Privileges in Code\n\n```php\n$user = auth()-\u003euser();\n\n// Check single privilege (uses Laravel's can() method)\nif ($user-\u003ecan('reports.run')) {\n    // User has the reports.run privilege\n}\n\n// Check multiple privileges (user must have ALL)\nif ($user-\u003ehasPrivileges(['reports.run', 'billing.view'])) {\n    // User has both privileges\n}\n\n// Get all privilege slugs\n$privileges = $user-\u003etyroPrivilegeSlugs(); // ['reports.run', 'billing.view']\n```\n\n### Managing Roles Programmatically\n\n```php\nuse HasinHayder\\Tyro\\Models\\Role;\n\n$user = User::find(1);\n\n// Assign a role\n$editorRole = Role::where('slug', 'editor')-\u003efirst();\n$user-\u003eassignRole($editorRole);\n\n// Remove a role\n$user-\u003eremoveRole($editorRole);\n\n// Get all roles as Eloquent models\n$roles = $user-\u003eroles;\n```\n\n### Managing Privileges Programmatically\n\n```php\nuse HasinHayder\\Tyro\\Models\\Role;\nuse HasinHayder\\Tyro\\Models\\Privilege;\n\n$role = Role::where('slug', 'editor')-\u003efirst();\n\n// Attach privileges to a role\n$privilege = Privilege::where('slug', 'reports.run')-\u003efirst();\n$role-\u003eprivileges()-\u003eattach($privilege-\u003eid);\n\n// Detach privileges\n$role-\u003eprivileges()-\u003edetach($privilege-\u003eid);\n\n// Check if role has a privilege\nif ($role-\u003ehasPrivilege('reports.run')) {\n    // Role has this privilege\n}\n```\n\n### User Suspension\n\n```php\n$user = User::find(1);\n\n// Suspend user (revokes all tokens automatically)\n$user-\u003esuspend('Pending account review');\n\n// Check if suspended\nif ($user-\u003eisSuspended()) {\n    $reason = $user-\u003egetSuspensionReason();\n}\n\n// Unsuspend user\n$user-\u003eunsuspend();\n```\n\n## Seeding (optional but recommended)\n\nTyro's `TyroSeeder` keeps every environment aligned by inserting the default roles, privileges, and bootstrap admin account. Trigger it manually or rerun it with `--force` any time you need to refresh local data:\n\n```bash\nphp artisan tyro:seed --force\n```\n\nRunning the seeder will:\n\n-   Insert the Administrator, User, Customer, Editor, All, and Super Admin roles along with their mapped privileges.\n-   Create the `admin@tyro.project` superuser (password `tyro`) so you always have a ready account.\n-   Reapply protected role/privilege relationships.\n\nNeed something narrower? Use `tyro:seed-roles` or `tyro:seed-privileges` to refresh a single catalog without touching users.\n\n#### HasTyroRoles Trait Reference\n\nThe `HasTyroRoles` trait gives your User model a complete API for roles, privileges, and suspensions. These methods are the same ones used by Tyro's routes and CLI commands, so your code stays consistent:\n\n| Method                                   | Category   | Description                                                                                           |\n| ---------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------- |\n| `roles(): BelongsToMany`                 | Roles      | Returns the eager-loadable relationship for roles. Chain additional constraints as needed.            |\n| `assignRole(Role $role): void`           | Roles      | Attaches a role without detaching existing ones.                                                      |\n| `removeRole(Role $role): void`           | Roles      | Detaches the given role from the user.                                                                |\n| `hasRole(string $role): bool`            | Roles      | Checks if the user has the specified role slug (supports wildcard `*`).                               |\n| `hasRoles(array $roles): bool`           | Roles      | Returns `true` only if the user holds every role in the array.                                        |\n| `tyroRoleSlugs(): array`                 | Roles      | Returns an array of all role slugs for the user (cached for performance).                             |\n| `privileges(): Collection`               | Privileges | Returns all unique privileges inherited through the user's roles.                                     |\n| `hasPrivileges(array $privileges): bool` | Privileges | Returns `true` only if the user has all specified privileges.                                         |\n| `hasPrivilege(string $privilege): bool`  | Privileges | Checks if the user has a specific privilege.                                                          |\n| `tyroPrivilegeSlugs(): array`            | Privileges | Returns an array of all privilege slugs for the user (cached for performance).                        |\n| `can($ability, $arguments = []): bool`   | Gate       | Checks privilege, then role, then falls back to Laravel Gate. Use this for unified permission checks. |\n| `suspend(?string $reason = null): void`  | Suspension | Suspends the user, stores optional reason, and revokes all Sanctum tokens.                            |\n| `unsuspend(): void`                      | Suspension | Clears suspension without touching roles or privileges.                                               |\n| `isSuspended(): bool`                    | Suspension | Returns `true` if the user is currently suspended.                                                    |\n| `getSuspensionReason(): ?string`         | Suspension | Returns the stored suspension reason (or `null`).                                                     |\n\nTyro caches role and privilege slugs per user so authorization checks never hit the database on every request. The cache respects your `config/tyro.php` settings and is automatically invalidated when you modify roles, privileges, or assignments.\n\n### 5. Optional configuration\n\nOverride defaults in `config/tyro.php` to align with your app:\n\n| Option                                   | Description                                                                                                   |\n| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------- |\n| `version`                                | Value returned by `/api/tyro/version`.                                                                        |\n| `disable_commands`                       | When `true` (or `TYRO_DISABLE_COMMANDS=true`) Tyro skips registering its artisan commands.                    |\n| `guard`                                  | Guard middleware used for protected routes (default `sanctum`).                                               |\n| `route_prefix`                           | Route prefix (default `api`).                                                                                 |\n| `disable_api`                            | When `true` (or `TYRO_DISABLE_API=true`) Tyro skips loading its built-in routes.                              |\n| `route_middleware`                       | Global middleware stack for package routes.                                                                   |\n| `models.user`                            | Fully qualified class name of your user model.                                                                |\n| `models.privilege`                       | Fully qualified class name of the privilege model (defaults to Tyro\\Models\\Privilege).                        |\n| `tables.roles/pivot`                     | Override the role table (default `roles`) or user-role pivot (default `user_roles`).                          |\n| `tables.users`                           | Table name Tyro targets when publishing its suspension columns (default `users`).                             |\n| `tables.privileges/role_privilege`       | Override the privilege table (default `privileges`) or the role-privilege pivot (default `privilege_role`).   |\n| `default_user_role_slug`                 | Role attached to new users (`user` by default).                                                               |\n| `protected_role_slugs`                   | Role slugs that cannot be mutated or deleted.                                                                 |\n| `delete_previous_access_tokens_on_login` | Enforce single-session logins when `true`.                                                                    |\n| `cache.enabled`                          | Toggle Tyro's per-user role/privilege cache (enabled by default).                                             |\n| `cache.store`                            | Choose which cache store to use for the helper cache (`null` falls back to Laravel's default store).          |\n| `cache.ttl`                              | Seconds to cache role/privilege slugs. `null` (or `\u003c= 0`) caches indefinitely until Tyro invalidates entries. |\n| `abilities.*`                            | Ability arrays checked by the middleware groups.                                                              |\n\nSet `load_default_routes` to `false` if you prefer to include `routes/api.php` manually and merge Tyro endpoints into your own files.\n\n### Disable Tyro commands or API via `.env`\n\nTyro registers a sizable CLI toolbox. If you would rather keep production shells lean (or limit what teammates can run), drop the following snippet into `.env` on the environments you wish to lock down:\n\n```\nTYRO_DISABLE_COMMANDS=true\n```\n\nWith the variable set to `true`, Tyro skips registering every `tyro:*` artisan command while continuing to expose routes, middleware, and config overrides as usual. Remove the line (or set it to `false`) locally to regain the commands for development.\n\nNeed to turn off the bundled API endpoints entirely? Set:\n\n```\nTYRO_DISABLE_API=true\n```\n\nWhen `TYRO_DISABLE_API` is `true`, Tyro skips loading its `routes/api.php` file so you can provide a fully custom HTTP surface (or disable it in worker contexts).\n\nNeed an emergency token rotation? Run `php artisan tyro:logout-all-users --force` to revoke every Sanctum token the package has issued.\n\n### Password Security\n\nTyro includes robust password validation that you can configure via your `.env` file. These settings apply to user registration, password updates, and the `tyro:create-user` command.\n\n| Environment Variable                  | Default | Description                                                                               |\n| ------------------------------------- | ------- | ----------------------------------------------------------------------------------------- |\n| `TYRO_PASSWORD_MIN_LENGTH`            | `8`     | Minimum number of characters required.                                                    |\n| `TYRO_PASSWORD_MAX_LENGTH`            | `null`  | Maximum number of characters allowed (optional).                                          |\n| `TYRO_PASSWORD_REQUIRE_NUMBERS`       | `false` | When `true`, passwords must contain at least one number.                                  |\n| `TYRO_PASSWORD_REQUIRE_UPPERCASE`     | `false` | When `true`, passwords must contain at least one uppercase letter.                        |\n| `TYRO_PASSWORD_REQUIRE_LOWERCASE`     | `false` | When `true`, passwords must contain at least one lowercase letter.                        |\n| `TYRO_PASSWORD_REQUIRE_SPECIAL_CHARS` | `false` | When `true`, passwords must contain at least one special character (symbol).              |\n| `TYRO_PASSWORD_REQUIRE_CONFIRMATION`  | `false` | When `true`, requires a matching `password_confirmation` field.                           |\n| `TYRO_PASSWORD_CHECK_COMMON`          | `false` | When `true`, prevents users from using common/compromised passwords (via standard lists). |\n| `TYRO_PASSWORD_DISALLOW_USER_INFO`    | `false` | When `true`, prevents passwords from containing the user's email or parts of their name.  |\n\nExample `.env` configuration for high security:\n\n```dotenv\nTYRO_PASSWORD_MIN_LENGTH=12\nTYRO_PASSWORD_REQUIRE_NUMBERS=true\nTYRO_PASSWORD_REQUIRE_SPECIAL_CHARS=true\nTYRO_PASSWORD_CHECK_COMMON=true\nTYRO_PASSWORD_DISALLOW_USER_INFO=true\n```\n\n## Blade Directives\n\nTyro provides custom Blade directives for checking user roles and privileges directly in your views. All directives automatically return `false` if no user is authenticated.\n\n### @usercan\n\nChecks if the current user has a specific role or privilege (uses the `can()` method):\n\n```blade\n@usercan('admin')\n    \u003cdiv class=\"admin-panel\"\u003e\n        \u003ch2\u003eAdmin Dashboard\u003c/h2\u003e\n        \u003cp\u003eWelcome to the admin area!\u003c/p\u003e\n    \u003c/div\u003e\n@endusercan\n\n@usercan('edit-posts')\n    \u003cbutton class=\"btn btn-primary\"\u003eEdit Post\u003c/button\u003e\n@endusercan\n```\n\n### @hasrole\n\nChecks if the current user has a specific role:\n\n```blade\n@hasrole('admin')\n    \u003cp\u003eWelcome, Admin!\u003c/p\u003e\n@endhasrole\n\n@hasrole('editor')\n    \u003ca href=\"/dashboard/editor\" class=\"nav-link\"\u003eEditor Dashboard\u003c/a\u003e\n@endhasrole\n```\n\n### @hasanyrole\n\nChecks if the current user has any of the provided roles:\n\n```blade\n@hasanyrole('admin', 'editor', 'moderator')\n    \u003cdiv class=\"management-tools\"\u003e\n        \u003ch3\u003eManagement Tools\u003c/h3\u003e\n        \u003cp\u003eYou have access to management features\u003c/p\u003e\n    \u003c/div\u003e\n@endhasanyrole\n```\n\n### @hasroles\n\nChecks if the current user has all of the provided roles:\n\n```blade\n@hasroles('admin', 'super-admin')\n    \u003cdiv class=\"super-admin-panel\"\u003e\n        \u003cp\u003eYou have both admin and super-admin privileges\u003c/p\u003e\n        \u003cbutton class=\"btn-danger\"\u003eCritical Actions\u003c/button\u003e\n    \u003c/div\u003e\n@endhasroles\n```\n\n### @hasprivilege\n\nChecks if the current user has a specific privilege:\n\n```blade\n@hasprivilege('delete-users')\n    \u003cbutton class=\"btn btn-danger\" onclick=\"deleteUser()\"\u003e\n        Delete User\n    \u003c/button\u003e\n@endhasprivilege\n\n@hasprivilege('view-reports')\n    \u003ca href=\"/reports\" class=\"nav-link\"\u003e\n        \u003ci class=\"icon-reports\"\u003e\u003c/i\u003e View Reports\n    \u003c/a\u003e\n@endhasprivilege\n```\n\n### @hasanyprivilege\n\nChecks if the current user has any of the provided privileges:\n\n```blade\n@hasanyprivilege('edit-posts', 'delete-posts', 'publish-posts')\n    \u003cdiv class=\"post-actions\"\u003e\n        \u003ch4\u003ePost Management\u003c/h4\u003e\n        @hasprivilege('edit-posts')\n            \u003cbutton\u003eEdit\u003c/button\u003e\n        @endhasprivilege\n        @hasprivilege('delete-posts')\n            \u003cbutton\u003eDelete\u003c/button\u003e\n        @endhasprivilege\n        @hasprivilege('publish-posts')\n            \u003cbutton\u003ePublish\u003c/button\u003e\n        @endhasprivilege\n    \u003c/div\u003e\n@endhasanyprivilege\n```\n\n### @hasprivileges\n\nChecks if the current user has all of the provided privileges:\n\n```blade\n@hasprivileges('create-invoices', 'approve-invoices')\n    \u003cbutton class=\"btn btn-success\" onclick=\"createAndApproveInvoice()\"\u003e\n        Create and Approve Invoice\n    \u003c/button\u003e\n@endhasprivileges\n\n@hasprivileges('view-reports', 'export-reports')\n    \u003cdiv class=\"reports-section\"\u003e\n        \u003ca href=\"/reports\"\u003eView Reports\u003c/a\u003e\n        \u003cbutton onclick=\"exportReport()\"\u003eExport\u003c/button\u003e\n    \u003c/div\u003e\n@endhasprivileges\n```\n\n### Combining Directives\n\nYou can nest and combine directives for complex authorization logic:\n\n```blade\n@hasrole('admin')\n    \u003cdiv class=\"admin-section\"\u003e\n        \u003ch2\u003eAdmin Controls\u003c/h2\u003e\n\n        @hasprivilege('manage-users')\n            \u003ca href=\"/admin/users\"\u003eManage Users\u003c/a\u003e\n        @endhasprivilege\n\n        @hasanyprivilege('view-reports', 'export-data')\n            \u003ca href=\"/admin/reports\"\u003eReports\u003c/a\u003e\n        @endhasanyprivilege\n    \u003c/div\u003e\n@endhasrole\n\n@hasanyrole('editor', 'author')\n    \u003cdiv class=\"content-tools\"\u003e\n        @hasprivilege('publish-posts')\n            \u003cbutton\u003ePublish\u003c/button\u003e\n        @else\n            \u003cbutton disabled\u003ePublish (requires approval)\u003c/button\u003e\n        @endhasprivilege\n    \u003c/div\u003e\n@endhasanyrole\n```\n\nAll directives leverage the methods from the `HasTyroRoles` trait and are automatically registered when the Tyro package is loaded. They provide a clean, readable way to conditionally display content based on user permissions without cluttering your Blade templates with PHP logic.\n\n## Middleware for Route Protection\n\nTyro ships with a complete set of middleware aliases for protecting your routes—whether you're building an API, web app, or both. These are registered automatically when you install the package.\n\n### Available Middleware\n\n| Middleware              | When to use it                                                                | Example                                    |\n| ----------------------- | ----------------------------------------------------------------------------- | ------------------------------------------ |\n| `auth:sanctum`          | Ensures the request is authenticated via Sanctum (or your configured guard).  | `auth:sanctum`                             |\n| `ability:comma,list`    | Require _all_ listed abilities (role slugs and/or privilege slugs).           | `'ability:admin,editor,reports.run'`       |\n| `abilities:comma,list`  | Allow access when the token has _any_ of the listed abilities.                | `'abilities:billing.view,finance.approve'` |\n| `role:comma,list`       | Require _all_ listed roles on the authenticated user (supports wildcard `*`). | `'role:admin,super-admin'`                 |\n| `roles:comma,list`      | Allow access when the user holds _any_ of the listed roles.                   | `'roles:editor,admin'`                     |\n| `privilege:comma,list`  | Require _all_ listed privileges directly on the authenticated user.           | `'privilege:reports.run,export.generate'`  |\n| `privileges:comma,list` | Allow access when the user has _any_ of the listed privileges.                | `'privileges:billing.view,reports.run'`    |\n| `tyro.log`              | Log request/response pairs for auditing.                                      | `'tyro.log'`                               |\n\n### Protecting Routes (Examples)\n\n```php\nuse Illuminate\\Support\\Facades\\Route;\n\n// Require user to be admin\nRoute::middleware(['auth:sanctum', 'role:admin'])\n    -\u003eget('admin/dashboard', AdminDashboardController::class);\n\n// Allow either editor or admin\nRoute::middleware(['auth:sanctum', 'roles:editor,admin'])\n    -\u003epost('articles/publish', PublishArticleController::class);\n\n// Require a specific privilege\nRoute::middleware(['auth:sanctum', 'privilege:reports.run'])\n    -\u003eget('reports', ReportsController::class);\n\n// Allow any of multiple privileges\nRoute::middleware(['auth:sanctum', 'privileges:billing.view,reports.run'])\n    -\u003eget('dashboard/widgets', DashboardController::class);\n\n// Audit sensitive routes\nRoute::middleware(['auth:sanctum', 'role:admin', 'tyro.log'])\n    -\u003edelete('users/{user}', [UserController::class, 'destroy']);\n```\n\n### Using Abilities in Policies\n\n```php\npublic function destroy(User $user, Report $report): bool\n{\n    return $user-\u003ehasRole('admin') || $user-\u003ecan('reports.delete');\n}\n```\n\n## CLI Commands (40+ Tools)\n\nTyro ships with a powerful CLI toolbox for managing users, roles, privileges, and tokens—perfect for automation, CI/CD pipelines, and incident response.\n\n### User Management Commands\n\n| Command                 | Purpose                                                                 |\n| ----------------------- | ----------------------------------------------------------------------- |\n| `tyro:create-user`      | Create a new user with name/email/password and attach the default role. |\n| `tyro:update-user`      | Update a user's details by ID or email.                                 |\n| `tyro:delete-user`      | Delete a user (prevents deleting the last admin).                       |\n| `tyro:users`            | List all users with suspension status.                                  |\n| `tyro:users-with-roles` | List all users with their assigned roles.                               |\n| `tyro:suspend-user`     | Suspend a user with optional reason (revokes all tokens).               |\n| `tyro:unsuspend-user`   | Remove suspension from a user.                                          |\n| `tyro:suspended-users`  | List all suspended users with reasons.                                  |\n\n### Role Management Commands\n\n| Command                      | Purpose                                            |\n| ---------------------------- | -------------------------------------------------- |\n| `tyro:roles`                 | Display all roles with user counts.                |\n| `tyro:roles-with-privileges` | Display roles with their attached privileges.      |\n| `tyro:create-role`           | Create a new role.                                 |\n| `tyro:update-role`           | Update a role's name or slug.                      |\n| `tyro:delete-role`           | Delete a role (protected roles cannot be deleted). |\n| `tyro:assign-role`           | Assign a role to a user.                           |\n| `tyro:delete-user-role`      | Remove a role from a user.                         |\n| `tyro:role-users`            | List all users with a specific role.               |\n| `tyro:user-roles`            | Display a user's roles and their privileges.       |\n\n### Privilege Management Commands\n\n| Command                 | Purpose                                           |\n| ----------------------- | ------------------------------------------------- |\n| `tyro:privileges`       | List all privileges and which roles have them.    |\n| `tyro:add-privilege`    | Create a new privilege.                           |\n| `tyro:update-privilege` | Update a privilege's name or slug.                |\n| `tyro:delete-privilege` | Delete a privilege.                               |\n| `tyro:attach-privilege` | Attach a privilege to a role.                     |\n| `tyro:detach-privilege` | Detach a privilege from a role.                   |\n| `tyro:user-privileges`  | Display all privileges a user inherits via roles. |\n\n### Authentication \u0026 Token Commands\n\n| Command                 | Purpose                                                      |\n| ----------------------- | ------------------------------------------------------------ |\n| `tyro:login`            | Mint a Sanctum token for a user (by ID or email).            |\n| `tyro:quick-token`      | Mint a token without password prompt (respects suspensions). |\n| `tyro:logout`           | Revoke a specific token.                                     |\n| `tyro:logout-all`       | Revoke all tokens for a specific user.                       |\n| `tyro:logout-all-users` | Revoke all tokens for all users (emergency rotation).        |\n| `tyro:me`               | Inspect a token to see user and abilities.                   |\n\n### Setup \u0026 Maintenance Commands\n\n| Command                   | Purpose                                                 |\n| ------------------------- | ------------------------------------------------------- |\n| `tyro:install`            | Full installation: migrations, seeds, user model setup. |\n| `tyro:prepare-user-model` | Add required traits to your User model.                 |\n| `tyro:seed`               | Run full seeder (roles, privileges, admin user).        |\n| `tyro:seed-roles`         | Seed only the default roles.                            |\n| `tyro:seed-privileges`    | Seed only the default privileges.                       |\n| `tyro:purge-roles`        | Remove all roles and assignments.                       |\n| `tyro:purge-privileges`   | Remove all privileges and assignments.                  |\n| `tyro:publish-config`     | Publish the config file.                                |\n| `tyro:publish-migrations` | Publish migration files.                                |\n| `tyro:version`            | Display current Tyro version.                           |\n| `tyro:about`              | Display Tyro info and links.                            |\n| `tyro:doc`                | Open documentation.                                     |\n| `tyro:star`               | Open GitHub to star the repo ⭐                         |\n| `tyro:postman-collection` | Open the Postman collection URL.                        |\n\nAll commands accept non-interactive `--option` flags, making them perfect for scripts and automation.\n\n## User Suspension\n\nTyro includes first-class user suspension support to freeze accounts without deleting them:\n\n```php\n// Suspend a user (revokes all tokens automatically)\n$user-\u003esuspend('Pending account review');\n\n// Check if suspended\nif ($user-\u003eisSuspended()) {\n    $reason = $user-\u003egetSuspensionReason();\n}\n\n// Unsuspend\n$user-\u003eunsuspend();\n```\n\n**CLI workflow:**\n\n```bash\n# Suspend a user\nphp artisan tyro:suspend-user --user=admin@example.com --reason=\"Manual review\"\n\n# View all suspended users\nphp artisan tyro:suspended-users\n\n# Unsuspend a user\nphp artisan tyro:unsuspend-user --user=admin@example.com\n```\n\nSuspended users cannot log in, and all their existing tokens are immediately revoked.\n\n## Optional: REST API Endpoints\n\nIf you need REST endpoints for managing users, roles, and privileges (useful for admin panels, mobile apps, etc.), Tyro includes a complete API surface. These are enabled by default but can be disabled if you don't need them.\n\n### Available Endpoints\n\nTyro registers the following endpoints (prefixed by `tyro.route_prefix`, default `api`):\n\n-   **Public:** `GET /tyro`, `GET /tyro/version`, `POST /login`, `POST /users` (registration)\n-   **Authenticated:** `GET /me`, `PUT|PATCH|POST /users/{user}`\n-   **Admin-only:** User CRUD, Role CRUD, Privilege CRUD, user-role assignments, role-privilege assignments, user suspension\n\n### Disabling the API\n\nIf you only need Tyro's code-level features (roles, privileges, CLI commands), disable the API:\n\n```env\nTYRO_DISABLE_API=true\n```\n\n### API Usage Examples\n\n#### Authentication\n\n```bash\n# Register a user\ncurl -X POST http://localhost/api/users \\\n\t-H \"Accept: application/json\" \\\n\t-H \"Content-Type: application/json\" \\\n\t-d '{\"name\":\"Jane User\",\"email\":\"jane@example.com\",\"password\":\"password\",\"password_confirmation\":\"password\"}'\n\n# Login\ncurl -X POST http://localhost/api/login \\\n\t-H \"Accept: application/json\" \\\n\t-H \"Content-Type: application/json\" \\\n\t-d '{\"email\":\"admin@tyro.project\",\"password\":\"tyro\"}'\n```\n\n#### Role Management (Admin)\n\n```bash\nTOKEN=\"\u003cyour-token\u003e\"\n\n# List roles\ncurl http://localhost/api/roles -H \"Authorization: Bearer ${TOKEN}\"\n\n# Attach a role to a user\ncurl -X POST http://localhost/api/users/5/roles \\\n\t-H \"Accept: application/json\" \\\n\t-H \"Content-Type: application/json\" \\\n\t-H \"Authorization: Bearer ${TOKEN}\" \\\n\t-d '{\"role_id\":4}'\n```\n\n#### Privilege Management (Admin)\n\n```bash\n# List privileges\ncurl http://localhost/api/privileges -H \"Authorization: Bearer ${TOKEN}\"\n\n# Create a privilege\ncurl -X POST http://localhost/api/privileges \\\n\t-H \"Accept: application/json\" \\\n\t-H \"Content-Type: application/json\" \\\n\t-H \"Authorization: Bearer ${TOKEN}\" \\\n\t-d '{\"name\":\"Run Reports\",\"slug\":\"reports.run\"}'\n\n# Attach privilege to role\ncurl -X POST http://localhost/api/roles/4/privileges \\\n\t-H \"Accept: application/json\" \\\n\t-H \"Content-Type: application/json\" \\\n\t-H \"Authorization: Bearer ${TOKEN}\" \\\n\t-d '{\"privilege_id\":2}'\n```\n\n## FAQ\n\n### How do I get the authenticated user's roles inside a controller?\n\n```php\nuse Illuminate\\Http\\Request;\n\nclass ProfileController\n{\n\tpublic function __invoke(Request $request)\n\t{\n\t\t$roleSlugs = $request-\u003euser()-\u003etyroRoleSlugs();\n\n\t\t// Or eager-load Role models when you need metadata\n\t\t$roles = $request-\u003euser()-\u003eroles()-\u003eselect(['id', 'name', 'slug'])-\u003eget();\n\n\t\treturn response()-\u003ejson(compact('roleSlugs', 'roles'));\n\t}\n}\n```\n\n### How do I get the authenticated user's privileges?\n\n```php\nuse Illuminate\\Http\\Request;\n\nclass ApiTokenController\n{\n\tpublic function show(Request $request)\n\t{\n\t\t$privileges = $request-\u003euser()-\u003etyroPrivilegeSlugs();\n\n\t\treturn response()-\u003ejson(['privileges' =\u003e $privileges]);\n\t}\n}\n```\n\n### How do I assign or remove roles to a user from code?\n\n```php\nuse HasinHayder\\Tyro\\Models\\Role;\nuse App\\Models\\User;\n\nclass UserRoleController\n{\n\tpublic function assignRoles()\n\t{\n\t\t$user = User::find(1);\n\n\t\t// Assign a single role\n\t\t$editorRole = Role::where('slug', 'editor')-\u003efirst();\n\t\t$user-\u003eassignRole($editorRole);\n\n\t\t// Assign multiple roles\n\t\t$adminRole = Role::where('slug', 'admin')-\u003efirst();\n\t\t$user-\u003eassignRole($adminRole);\n\n\t\t// Or use the roles relationship directly\n\t\t$customerRole = Role::where('slug', 'customer')-\u003efirst();\n\t\t$user-\u003eroles()-\u003eattach($customerRole-\u003eid);\n\t}\n\n\tpublic function removeRoles()\n\t{\n\t\t$user = User::find(1);\n\n\t\t// Remove a single role\n\t\t$editorRole = Role::where('slug', 'editor')-\u003efirst();\n\t\t$user-\u003eremoveRole($editorRole);\n\n\t\t// Or use the roles relationship directly\n\t\t$user-\u003eroles()-\u003edetach($editorRole-\u003eid);\n\n\t\t// Remove all roles\n\t\t$user-\u003eroles()-\u003edetach();\n\t}\n}\n```\n\n### How do I assign or remove privileges to a role?\n\n```php\nuse HasinHayder\\Tyro\\Models\\Role;\nuse HasinHayder\\Tyro\\Models\\Privilege;\n\nclass RolePrivilegeController\n{\n\tpublic function assignPrivileges()\n\t{\n\t\t$role = Role::where('slug', 'editor')-\u003efirst();\n\n\t\t// Assign a single privilege\n\t\t$reportPrivilege = Privilege::where('slug', 'reports.run')-\u003efirst();\n\t\t$role-\u003eprivileges()-\u003eattach($reportPrivilege-\u003eid);\n\n\t\t// Assign multiple privileges at once\n\t\t$billingPrivilege = Privilege::where('slug', 'billing.view')-\u003efirst();\n\t\t$exportPrivilege = Privilege::where('slug', 'reports.export')-\u003efirst();\n\t\t$role-\u003eprivileges()-\u003eattach([\n\t\t\t$billingPrivilege-\u003eid,\n\t\t\t$exportPrivilege-\u003eid,\n\t\t]);\n\n\t\t// Or sync privileges (replaces all existing privileges)\n\t\t$role-\u003eprivileges()-\u003esync([\n\t\t\t$reportPrivilege-\u003eid,\n\t\t\t$billingPrivilege-\u003eid,\n\t\t]);\n\t}\n\n\tpublic function removePrivileges()\n\t{\n\t\t$role = Role::where('slug', 'editor')-\u003efirst();\n\n\t\t// Remove a single privilege\n\t\t$reportPrivilege = Privilege::where('slug', 'reports.run')-\u003efirst();\n\t\t$role-\u003eprivileges()-\u003edetach($reportPrivilege-\u003eid);\n\n\t\t// Remove multiple privileges\n\t\t$role-\u003eprivileges()-\u003edetach([\n\t\t\t$reportPrivilege-\u003eid,\n\t\t\t$billingPrivilege-\u003eid,\n\t\t]);\n\n\t\t// Remove all privileges from the role\n\t\t$role-\u003eprivileges()-\u003edetach();\n\t}\n}\n```\n\n### How do I get the list of privileges in a role?\n\n```php\nuse HasinHayder\\Tyro\\Models\\Role;\n\nclass RolePrivilegesController\n{\n\tpublic function show(Role $role)\n\t{\n\t\t// Load privileges relationship\n\t\t$role-\u003eloadMissing('privileges:id,name,slug');\n\n\t\treturn response()-\u003ejson([\n\t\t\t'role' =\u003e $role-\u003eonly(['id', 'name', 'slug']),\n\t\t\t'privileges' =\u003e $role-\u003eprivileges,\n\t\t]);\n\t}\n\n\tpublic function getPrivilegeSlugs(Role $role)\n\t{\n\t\t// Get only the privilege slugs as an array\n\t\t$privilegeSlugs = $role-\u003eprivileges()-\u003epluck('slug')-\u003etoArray();\n\n\t\treturn response()-\u003ejson(['privilege_slugs' =\u003e $privilegeSlugs]);\n\t}\n}\n```\n\n### How do I check if a role has specific privileges?\n\nThe `Role` model includes `hasPrivilege()` and `hasPrivileges()` methods for checking privileges:\n\n```php\nuse HasinHayder\\Tyro\\Models\\Role;\n\nclass RoleCheckController\n{\n\tpublic function checkPrivileges()\n\t{\n\t\t$role = Role::where('slug', 'editor')-\u003efirst();\n\n\t\t// Check if role has a single privilege\n\t\tif ($role-\u003ehasPrivilege('reports.run')) {\n\t\t\t// Role has the reports.run privilege\n\t\t}\n\n\t\t// Check if role has ALL specified privileges\n\t\tif ($role-\u003ehasPrivileges(['reports.run', 'billing.view'])) {\n\t\t\t// Role has both reports.run AND billing.view privileges\n\t\t}\n\n\t\t// Check if role has ANY of the specified privileges\n\t\t$hasAny = $role-\u003eprivileges()\n\t\t\t-\u003ewhereIn('slug', ['reports.run', 'billing.view'])\n\t\t\t-\u003eexists();\n\t}\n}\n```\n\n### How do I check if the authenticated user has particular roles?\n\n```php\nuse Illuminate\\Http\\Request;\n\nclass ArticleController\n{\n\tpublic function store(Request $request)\n\t{\n\t\tif (! $request-\u003euser()-\u003ehasRoles(['editor', 'admin'])) {\n\t\t\tabort(403, 'Editors or admins only.');\n\t\t}\n\n\t\t// Create the article\n\t}\n\n\tpublic function destroy(Request $request)\n\t{\n\t\tif (! $request-\u003euser()-\u003ehasRole('super-admin')) {\n\t\t\tabort(403, 'Super admins only.');\n\t\t}\n\n\t\t// Delete the article\n\t}\n}\n```\n\n### How do I check if the authenticated user has specific privileges?\n\n```php\nuse Illuminate\\Http\\Request;\n\nclass BillingReportController\n{\n\tpublic function index(Request $request)\n\t{\n\t\tif (! $request-\u003euser()-\u003ehasPrivileges(['reports.run', 'billing.view'])) {\n\t\t\tabort(403, 'Missing reporting privileges.');\n\t\t}\n\n\t\t// Build the report\n\t}\n\n\tpublic function export(Request $request)\n\t{\n\t\tif (! $request-\u003euser()-\u003ecan('reports.export')) {\n\t\t\tabort(403, 'Missing export privilege.');\n\t\t}\n\n\t\t// Return the file download\n\t}\n}\n```\n\n### How do I check if a user is suspended and inspect the reason?\n\n```php\nuse Illuminate\\Http\\Request;\n\nclass LoginStatusController\n{\n\tpublic function __invoke(Request $request)\n\t{\n\t\t$user = $request-\u003euser();\n\n\t\tif ($user-\u003eisSuspended()) {\n\t\t\treturn response()-\u003ejson([\n\t\t\t\t'suspended' =\u003e true,\n\t\t\t\t'reason' =\u003e $user-\u003egetSuspensionReason(),\n\t\t\t], 423);\n\t\t}\n\n\t\treturn response()-\u003ejson(['suspended' =\u003e false]);\n\t}\n}\n```\n\n## Database assets\n\n-   `database/migrations/*` creates the `roles`, `user_roles`, `privileges`, and `privilege_role` tables (configurable via `config/tyro.php`).\n-   `database/seeders/TyroSeeder` seeds the core roles, default privileges, and creates the admin bootstrap user.\n-   `database/factories/UserFactory` targets whichever user model you configure, and `PrivilegeFactory` speeds up testing custom privileges.\n\n## Development\n\n-   `src/Providers/TyroServiceProvider.php` handles route loading, publishing, and middleware aliases.\n-   Controllers live under `src/Http/Controllers/*` and operate against the configurable user model.\n-   `routes/api.php` declares all endpoints and ability middleware in one place.\n\nContributions are welcome! Please open an issue or pull request with improvements, bug fixes, or new ideas.\n\n## License\n\nTyro is open-sourced software licensed under the [MIT license](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhasinhayder%2Ftyro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhasinhayder%2Ftyro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhasinhayder%2Ftyro/lists"}