Repository navigation
Architecture and Schema
HasAccess delegates to the stateless Services\AccessService. Eloquent models Models\AccessPermission, Models\AccessRole and Models\AccessTeam expose persisted definitions. AccessServiceProvider merges configuration and registers explicit publication tags. It installs no routes, policies, Gate hooks, commands, jobs, workers or caches.
All names below begin with moztopia_access_. Definition entities use canonical UUIDv7 string primary keys generated by Laravel's HasUuids, plus unique name, created_at and updated_at. Teams also have display_name.
-
permissions: named ability definitions; uniquename. -
roles: named role definitions; uniquename. -
teams: membership groups with unique stablenameand mutabledisplay_name; display labels need not be unique. -
role_permissions: composite primary key(role_id, permission_id), both foreign keys cascade deletion. -
memberships: composite primary key(team_id, user_id); team and host-user foreign keys cascade deletion. A row means active membership; there is no inactive membership or soft-delete state. -
role_assignments:user_id,role_id, nullableteam_id, generatedscope_key; unique(user_id, scope_key, role_id). -
permission_assignments: analogous, withpermission_id.
The last four tables represent relationships with composite UUID identities, not additional ordered entities. They need no surrogate IDs or audit timestamps. Each assignment references its definition and host user. Composite foreign key (team_id, user_id) references active membership and cascades its removal. SQL's nullable foreign-key behavior permits global assignments where team_id is null.
scope_key is a stored generated UUID-compatible column equal to coalesce(rtrim(team_id), '00000000-0000-0000-0000-000000000000'). Its non-null sentinel makes global grants atomically unique even on databases whose unique constraints permit multiple nulls. It is derived by the database, never supplied by callers. The sentinel is internal; it is not a team and is never used as a definition's generated identity. RTRIM keeps the expression stable across MariaDB character-padding modes. See MariaDB's generated-column requirements.
The configured Eloquent authenticatable determines the host table and primary-key column at migration time. Its schema must already exist, with compatible UUID string keys. All tables share the default connection. The package does not migrate, rename or add columns to the host table. Do not change user model/table/key/connection configuration after installation without a separately designed migration.
All service writes run in transactions, with Laravel retrying recognized deadlocks up to five attempts. For user mutations on MariaDB, the service locks the host user row FOR UPDATE, then the team row when supplied, then the accessed definition. Add/remove membership uses the same user-then-team lock order. Team updates/deletion lock the team; role-set changes lock the role. Definition deletion is protected by its row lock and foreign-key cascades. Database constraints remain the final integrity boundary when deletion or another write races.
SQLite ignores row-level FOR UPDATE. Before reading inside each mutation transaction, Access executes a no-match write to its own roles table to obtain SQLite's database-wide writer lock. SQLite mutations are consequently serialized across the database, with configured busy timeout/retry behavior. No user data is modified just to obtain a lock. SQLite is suitable for modest write concurrency; use MariaDB when concurrent writers are expected.
Duplicate grants use an upsert with the database's unique assignment key. Concurrent membership removal versus granting has two valid outcomes: the grant commits first and is subsequently cascaded away, or removal commits first and the grant is rejected for missing membership. A rejoin creates membership only and never recreates assignments.
Checks issue fresh queries and do not retain effective-access state. Each effective permission query combines direct/role access and membership in one SQL statement. Queries may observe an earlier committed snapshot while another transaction is in flight; checks do not reserve authority for a later business write. A host transaction's isolation level may also retain an earlier snapshot. If a business operation must exclude concurrent revocation, the host must coordinate its authorization check and business write with appropriate transaction/locking rules. The package does not promise to recall work already authorized, override transaction isolation, or secure arbitrary raw trusted SQL.
Named team parameters resolve an existing unique team name to its UUID. Mutation lookups lock the team row in the same transaction; checks resolve without retaining state. Unknown names never create teams or memberships.
The supported write boundary is AccessService/HasAccess. Eloquent models and relationship queries are useful for reading, but direct relationship attach, model deletion, raw SQL and withoutForeignKeyConstraints bypass service-level validation/locking. Do not expose those paths as management endpoints. FK cascades and unique keys still protect normal constrained SQL; arbitrary trusted code can disable or circumvent those protections.
No persistent cache, Eloquent relationship cache or global current-team state is used by permission checks. Host applications are responsible for any caching or loaded teams collections they choose to retain.
Copyright © 2026 Moztopia · Source · Licensing status · Security