https://github.com/whilesmartphp/eloquent-owner-access
https://github.com/whilesmartphp/eloquent-owner-access
Last synced: about 1 month ago
JSON representation
- Host: GitHub
- URL: https://github.com/whilesmartphp/eloquent-owner-access
- Owner: whilesmartphp
- License: mit
- Created: 2026-04-25T06:25:58.000Z (3 months ago)
- Default Branch: dev
- Last Pushed: 2026-06-10T17:47:54.000Z (about 2 months ago)
- Last Synced: 2026-06-10T19:17:05.216Z (about 2 months ago)
- Language: PHP
- Size: 14.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# eloquent-owner-access
Pluggable polymorphic owner authorization for Laravel packages with `owner_type` / `owner_id` columns.
Domain packages (accounts, expenses, payments, invoices, products, customers, ...) expose CRUD endpoints scoped by a polymorphic owner. They should not have to know how the host app decides who owns what. This package gives them a single contract to consult and a default that preserves existing "trust the client" behavior, so adoption is incremental and safe.
## Concept
One contract:
```php
interface OwnerAuthorizer
{
public function authorize(?Authenticatable $user, string $ownerType, mixed $ownerId): bool;
public function scope(
Builder $query,
?Authenticatable $user,
string $ownerTypeColumn = 'owner_type',
string $ownerIdColumn = 'owner_id',
): Builder;
}
```
`authorize()` is per-record (called from `FormRequest::authorize()` for store/update and from controllers for show/destroy). `scope()` constrains index queries.
The default binding is `AllowAllAuthorizer` (everything goes through). Hosts that want strict tenancy bind their own implementation.
## Installation
```bash
composer require whilesmart/eloquent-owner-access
```
The service provider auto-registers via Laravel package discovery and `bindIf`s the default authorizer.
## Usage in a package
In a `StoreXRequest`:
```php
use Whilesmart\OwnerAccess\Concerns\AuthorizesOwnerRequest;
class StoreAccountRequest extends FormRequest
{
use AuthorizesOwnerRequest;
public function authorize(): bool
{
return $this->authorizeOwnerInRequest();
}
public function rules(): array { /* ... */ }
}
```
In an `UpdateXRequest`:
```php
public function authorize(): bool
{
return $this->authorizeOwnerOfBoundModel('account');
}
```
In the controller:
```php
use Whilesmart\OwnerAccess\Concerns\AuthorizesOwnerController;
class AccountController extends Controller
{
use AuthorizesOwnerController;
public function index(Request $request)
{
$query = Account::query();
$query = $this->scopeAccessibleOwners($query, $request->user());
// ...
}
public function show(Account $account, Request $request)
{
$this->authorizeAccessTo($account, $request->user());
// ...
}
public function destroy(Account $account, Request $request)
{
$this->authorizeAccessTo($account, $request->user());
$account->delete();
// ...
}
}
```
Update is already covered by `UpdateXRequest::authorize()`, so no controller change is needed there.
## Usage in a host app
By default everything is allowed. To enforce tenancy, write an authorizer that consults your tenancy model and bind it:
```php
namespace App\Authorization;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Whilesmart\OwnerAccess\Contracts\OwnerAuthorizer;
class WorkspaceMemberAuthorizer implements OwnerAuthorizer
{
public function authorize(?Authenticatable $user, string $ownerType, mixed $ownerId): bool
{
if ($user === null || $ownerType !== \App\Models\Workspace::class) {
return false;
}
return $user->workspaces()->whereKey($ownerId)->exists();
}
public function scope(Builder $query, ?Authenticatable $user, string $ownerTypeColumn = 'owner_type', string $ownerIdColumn = 'owner_id'): Builder
{
if ($user === null) {
return $query->whereRaw('0 = 1');
}
return $query->where($ownerTypeColumn, \App\Models\Workspace::class)
->whereIn($ownerIdColumn, $user->workspaces()->pluck('workspaces.id'));
}
}
```
Then in `AppServiceProvider::register()`:
```php
$this->app->bind(
\Whilesmart\OwnerAccess\Contracts\OwnerAuthorizer::class,
\App\Authorization\WorkspaceMemberAuthorizer::class,
);
```
That single binding switches every consuming package over to strict mode at once.
## Backwards compatibility
The provider uses `bindIf`, so any explicit binding the host registers (before or after) wins. Without an explicit binding, the default `AllowAllAuthorizer` keeps current behavior.
## Testing
```bash
composer test
```
## License
MIT.