wobqqq/nova-aegis
Nova Aegis is a Laravel Nova package for role-based access control, letting you manage roles, permissions, and user abilities from a simple Nova interface. It helps protect resources and keep authorization consistent without custom boilerplate.
This package gives you a settings store (persisted, transactional) plus a scanner/checks/audit layer and a JSON API. Start by reading config/ before touching any code — the scanner's target allow-list is the single most important thing to locate, because anything not listed there is rejected outright.
Minimal path:
composer require the package and publish config + migrations (php artisan vendor:publish, then php artisan migrate).targets (or equivalently named) array. Add at least one entry — an empty or mismatched list means every scan call returns a 422.GET/PUT on the settings endpoint) to confirm routing, auth middleware, and validation are wired the way you expect.SettingsSaved and Log::info() the payload. This validates the write path end-to-end and shows you the event's shape before you build on it.First real use case: persist a group of related settings in one API call and invalidate a cache key from the SettingsSaved listener. That exercises the transaction, the event, and your own side effects in one go.
Treat the API as the write path, not the model. The package owns validation via form requests and owns the transaction boundary. Calling the service or the endpoint keeps those guarantees; writing to the settings table directly bypasses validation and the event entirely.
Use SettingsSaved for fan-out, not for consistency. Because the event now fires after commit, it is the correct place for anything that must not observe rolled-back values: cache invalidation, dispatching jobs, syncing to an external service, firing webhooks. If a listener throws, the settings are still saved — design listeners to be idempotent and independently retryable.
Keep the transaction for atomicity, the event for effects. When one save must also mutate your own tables atomically, do that inside a DB::transaction you control and let the settings write happen within it; the event will still arrive afterwards, once, after the outermost commit.
Register targets explicitly and centrally. Keep the allow-list in config rather than scattering registration across service providers, so a code reviewer can see the full scanner surface in one diff. If you need dynamic targets, add them in a single service provider boot method and keep the ordering deterministic.
Exploit the injected PSR clock in tests. Bind a fake clock into the container to pin the time that checks and the audit see. This makes audit assertions exact rather than "within a minute," and it lets you test expiry/TTL boundaries without sleep().
Extend the API by subclassing the form request, not by adding validation in a controller or middleware. Override rules() for extra fields and remember to keep authorize() meaningful — a fresh override returns false and will 403 everything.
SettingsSaved is post-commit — this is the one real behavior change in 2.1.0. If any listener previously relied on running inside the transaction (reading uncommitted rows it had just written, or rolling the save back by throwing), that no longer works: the throw is now swallowed from the persistence path, and the rows are already durable. Move such logic into an explicit transaction around the save call.
Unlisted targets return 422, not 404. The scanner now refuses with TargetNotListed and the same 422 body as before. Do not build client retry logic on 404, and do not assume a 422 means "bad input" — check the error code/message to distinguish a validation failure from an unregistered target. The most common cause of a sudden 422 in a working app is a target name typo or a config cache that wasn't refreshed.
Clear config cache after deploys. php artisan config:cache freezes the allow-list. Adding a target to config and not re-caching is the single most frequent "it works locally" failure with this package.
Queued listeners plus after-commit ordering. A queued listener on SettingsSaved is now dispatched after commit, so afterCommit on the job is redundant but harmless. A sync listener, however, runs before the HTTP response returns — keep it fast, or mark it ShouldQueue and let the response go.
Checks and the audit read the injected clock, not now(). Carbon::setTestNow() will not affect them if they've resolved a PSR-20 clock from the container. Bind your fake clock in the container (or use the package's testing helper, if provided) or your time-based assertions will silently drift.
Debugging a failing scan: check the allow-list first, then the clock, then the payload shape. The refactor to form requests means invalid input now fails before the scanner runs, so an unexpected 422 may be pure validation and never reach the TargetNotListed path at all.
Extension points to prefer: config for the target list, container bindings for the clock, form-request subclasses for input, and event listeners for side effects. Avoid decorating the scanner itself unless you need to change refusal semantics — the 422 contract is relied on by clients.
How can I help you explore Laravel packages today?