Request middleware
Global middleware sets JSON responses and the request locale. Protected package routes use the parentexowpee.teams and an access group.
Controllers then validate input and call services, which authorize through policies
and return resources.
Scope context parent
exowpee.teams is the alias for IAM’s TeamPermissions.
It clears the previous Spatie team ID, stored request context and loaded permission
relations. For an authenticated token it checks owner claims against iam_scopes,
then sets the selected owner ID as Spatie’s team ID.
A token cannot carry both a company and a client selection.
The scope’s type and organization must match the authenticated guard and claims.
The parent fills context; it does not authorize business actions.
Complete access groups
Each composes
auth:<guards>, exowpee.set.guard, exowpee.context,
exowpee.active.actor and exowpee.verified.user.
The four guards retain separate provider/type checks. Active-actor middleware
validates the session key, actor and selected owner/profile/membership.
Verification applies to human users, not agents.
A null or mismatched stored session hash is rejected with 401.
Raw auth:api,agent and guard selection alone omit these protections.
Existing manually composed routes must include equivalent checks; use a complete
group when adding routes.
The access context middleware checks allowed owner and actor types, not whether
an owner has been selected. Shared human self-service and profile switching work
with an unscoped login token. Scoped services must resolve currentScope() and
authorize their resources.
Helpers after authentication
auth()->user()returns the guard’s User, ClientUser, Agent or ClientAgent model.auth()->company()resolves company context; client guards do not supply a company.auth()->client()resolves client context for client guards.auth()->profile()resolves the selected, scope-consistent profile.auth()->currentUserProfile()resolves the human assignment.getTeamOrganization()resolves the selected scope’s organization.
getPermissionsTeamId() in shared IAM code.
See IAM overview for actor/context mapping.
Validation layer
FormRequest
Each endpoint has a dedicatedFormRequest class that:
- Validates input — Returns
422 Unprocessable Entitywith field-specific errors - Authorizes the action — Can return
403 Forbidden
ValidationErrorFormatter for consistent, human-readable messages.
Authorization layer
Policies
Every model has aPolicy class that defines who can perform each action:
BeforePolicyFilterTrait denies an inactive actor, then permits the
administrator role before checking working hours or the action method.
IAM and Team services call a separate protection Gate before the permission
Gate. IamProtectionPolicy and TeamProtectionPolicy do not use
BeforePolicyFilterTrait. They enforce company/client and organization scope,
builtin immutability, and ownership of the active default-profile preference.
The permission policies use the shared trait directly, without overriding
before().
For example, deleting a custom profile requires both checks:
iam-protection.* and team-protection.* Gates are internal rules. They are
not discovered or granted as permissions. Every affected service operation must
call both Gates; calling the permission Gate alone does not enforce protection.
Default changes use iam-protection.default; changing hours, activation or
deletion of an assignment uses iam-protection.assignment. Builtin assignments
allow only the user’s own active default preference.
A future module entitlement check, such as access to accounting under a paid
plan, can use another explicit Gate without the administrator shortcut. It would
check the subscription of the selected company/client before the operation’s
permission Gate. No subscription check is implemented in this change.
Business logic layer
Service classes
Controllers are kept thin — they delegate to Service classes for business logic:- Complex creation logic (e.g., product + variants + images in one operation)
- Cross-model operations (e.g., stock updates when confirming an order)
- Event dispatching
- Integration with other packages via contracts
Response layer
API Resources
Every response is transformed through aResource class that controls which fields are exposed:
Refresh broadcasts
Notification implements queuedClientRefreshEvent broadcasts for company and
client scopes. Profile switching resets the actor’s read set and broadcasts a
refresh signal. Automatic business invalidation is not wired into production
callers yet. See broadcasting for the implemented
channels, worker requirements and revocation limitation.