1d4ac3db380697ccc818d2538db893c2a62bb355
Completes the P1 items from /tmp/service/new/01-TZ-rbac §2.1 §4.1. == user_permission_overrides table == Per-user grant/deny exceptions on top of role-based RBAC. Composite PK (user_id, permission_id) so each user can have at most one override per permission. Schema: - mode: 'grant' | 'deny' - reason: text (audit context: "lockdown audit period", etc.) - granted_by_id + granted_at: who/when made the exception - expires_at: optional auto-expiry Eloquent model UserPermissionOverride with relations to user, permission, grantedBy; isExpired() helper. == Resolution order in User::canDo() == 1. Active deny-override (not expired) → return false (and log if sensitive) 2. Active grant-override (not expired) → return true 3. Admin/owner bypass → return true 4. Standard role-based check via Spatie Critically: deny overrides ALSO block admin/owner. This is intentional — the TZ's "separation of duties" requirement (an admin who shouldn't be able to delete payments). Without this, deny is useless against admins. Override resolution uses a single query per check (cached by Eloquent during the request). The override-check happens before the role check so a deny is always authoritative. == Audit log on sensitive denials == When canDo() returns false for one of these sensitive permissions, a spatie/activitylog entry is written with event=permission_denied: - admin.users.manage / admin.roles.manage / admin.settings.edit / admin.backup.download - finance.delete_payment / finance.view_pl - salaries.mark_paid / salaries.view_all - work_orders.delete / work_orders.approve_discount_any Non-sensitive denials (e.g., clients.create) don't log to avoid noise. The activity payload includes the permission slug; causedBy is the user who was denied. Failures of the logger are swallowed so a misconfigured activitylog never breaks auth. == UserResource UI == New PermissionOverridesRelationManager mounted on the edit page: - Table: permission, mode (GRANT/DENY badge), reason, expires_at, granted_by - Create form: permission select, mode, expires_at, reason - granted_at + granted_by_id auto-populated to now() / auth()->id() - Default sort: granted_at desc Two new actions on the user row: - "Force logout" (warning color): visible only when active sessions exist. Deletes every row in `sessions` with user_id=record→id. Notification shows count revoked. - "Resetează 2FA" stays (from previous commit) Two new toggleable columns: - Sesiuni active (count from sessions table) - Excepții (count of permission overrides) == Tests == PermissionOverridesTest covers: - grant unlocks a permission the role doesn't have - deny blocks a permission the role grants - deny blocks even admin role (separation of duties) - expired override is ignored - future-expiry override stays active - audit log writes on sensitive denial - audit log silent on non-sensitive denial - force_logout deletes all user sessions but not others' Suite: 214 passed (591 assertions). Was 206. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AutoCRM
SaaS CRM multi-tenant pentru autoservice și magazin piese auto.
Stack
- Laravel 12 + Filament 5
- Octane (FrankenPHP runtime)
- MariaDB 11.4
- Redis 7
- Stancl/Tenancy v3 (single-DB multi-tenant)
Domenii
service.mir.md— panou central super-admin<slug>.service.mir.md— instanță tenant
Documentație
Vezi /root/service/docs/00-index.md în repo-ul părinte (separat de cod) pentru:
- Arhitectură + decizii
- Setup multi-tenant (3 niveluri izolare)
- Toate cele 31 module preluate din prototip
- Operațiuni (deploy / backup / add-tenant / troubleshoot)
Dev local
cp .env.example .env
composer install
npm install
php artisan key:generate
php artisan migrate
npm run build
php artisan octane:start --server=frankenphp
Deploy
Push pe main → Coolify auto-deploy.
License
Proprietary. © 2026.
Description
Languages
PHP
78.5%
Blade
21.2%
Dockerfile
0.2%
Shell
0.1%