registerPolicies(); // Set remember me duration from platform configuration $rememberMeDays = timebank_config('auth.remember_me_days', 90); $rememberMeMinutes = $rememberMeDays * 24 * 60; // Convert days to minutes // Use custom guard in Docker that doesn't migrate sessions if (env('IS_DOCKER', false)) { Auth::extend('session', function ($app, $name, array $config) use ($rememberMeMinutes) { $provider = Auth::createUserProvider($config['provider']); $guard = new DockerSessionGuard($name, $provider, $app['session.store']); // Set the cookie jar on the guard $guard->setCookieJar($app['cookie']); // If a request is available, set it on the guard if (method_exists($guard, 'setRequest')) { $guard->setRequest($app->refresh('request', $guard, 'setRequest')); } // Set remember me duration $guard->setRememberDuration($rememberMeMinutes); return $guard; }); } // Set remember me duration for all standard guards foreach (['web', 'organization', 'bank', 'admin'] as $guardName) { $guard = Auth::guard($guardName); if (method_exists($guard, 'setRememberDuration')) { $guard->setRememberDuration($rememberMeMinutes); } } // All permissions are strictly scoped to the active profile session. // // Rules: // - Active profile = Admin{id}: only the scoped role Admin\{id}\{roleName} // determines permissions. Roles from other linked admin profiles are ignored. // - Active profile = Organization/Bank: only scoped Org/Bank roles apply. // Admin-level permissions are NOT accessible when acting as Org/Bank. // - Active profile = User or null/unknown: no admin permissions granted. // Shared factory: returns a Gate closure that checks $permission on the active // profile's single scoped role. Used for both manage and granular verbs. $makeGate = function (string $permission) { return function ($user) use ($permission) { // Roles live on the User model (web guard). When the active guard is // 'admin' (or another profile guard), Gate receives an Admin/Bank/Org // model instead. Always resolve the web user explicitly. if (!($user instanceof \App\Models\User)) { $user = \Illuminate\Support\Facades\Auth::guard('web')->user(); if (!($user instanceof \App\Models\User)) { return false; } } try { $activeType = session('activeProfileType'); $activeId = session('activeProfileId'); if (!$activeType || !$activeId) { return false; } $prefixMap = [ 'Admin' => "Admin\\{$activeId}\\", 'Organization' => "Organization\\{$activeId}\\", 'Bank' => "Bank\\{$activeId}\\", ]; $activeClass = class_basename($activeType); $prefix = $prefixMap[$activeClass] ?? null; if (!$prefix) { return false; // User profile or unknown: no permissions } $scopedRoles = $user->roles ->filter(fn($r) => str_starts_with($r->name, $prefix)); if ($scopedRoles->isEmpty()) { return false; } // Grant if any of the user's scoped roles for this profile has the permission. // Also honour the manage→verb implication for granular checks. $managePermission = null; $parts = explode(' ', $permission, 2); if (count($parts) === 2 && $parts[0] !== 'manage') { $managePermission = 'manage ' . $parts[1]; } foreach ($scopedRoles as $scopedRole) { try { if ($scopedRole->hasPermissionTo($permission, 'web')) { return true; } } catch (\Spatie\Permission\Exceptions\PermissionDoesNotExist $e) { // permission doesn't exist in DB, skip } if ($managePermission) { try { if ($scopedRole->hasPermissionTo($managePermission, 'web')) { return true; } } catch (\Spatie\Permission\Exceptions\PermissionDoesNotExist $e) { // manage permission doesn't exist, skip } } } return false; } catch (\Spatie\Permission\Exceptions\PermissionDoesNotExist $e) { return false; } catch (\Exception $e) { return false; } }; }; // manage {resource} — umbrella permission (checked by route middleware + mount()) foreach ([ 'banks', 'organizations', 'admins', 'calls', 'posts', 'polls', 'tags', 'categories', 'mailings', 'profiles', 'roles', 'permissions', 'users', 'user_projects', 'accounts', 'maintenance', ] as $resource) { Gate::define("manage {$resource}", $makeGate("manage {$resource}")); } // Granular action Gates — checked inside individual Livewire methods and blade views. // Each resource lists only the verbs that make sense for it. $granular = [ 'posts' => ['view', 'create', 'update', 'publish', 'unpublish', 'delete'], // Polls: 'export' covers who voted (Results tab) and the CSV export 'polls' => ['view', 'create', 'update', 'export', 'delete'], 'tags' => ['view', 'create', 'update', 'delete'], 'categories' => ['view', 'create', 'update', 'delete'], 'calls' => ['view', 'create', 'update', 'publish', 'unpublish', 'delete'], 'mailings' => ['view', 'create', 'update', 'send', 'delete'], 'users' => ['view', 'create', 'update', 'delete'], 'user_projects' => ['view', 'create', 'update', 'delete'], 'organizations' => ['view', 'create', 'update', 'delete'], 'banks' => ['view', 'create', 'update', 'delete'], 'admins' => ['view', 'create', 'update', 'delete'], 'accounts' => ['view', 'create', 'update', 'delete'], 'profiles' => ['view', 'create', 'update', 'delete'], 'permissions' => ['view', 'create', 'update', 'delete'], 'roles' => ['view', 'create', 'update', 'delete'], 'search-metrics' => ['view'], 'application-log' => ['view'], 'inactive-profiles-log' => ['view'], 'search-reindex-log' => ['view'], 'stats' => ['view'], ]; // Org/bank capability gates — checked inside org-context Livewire components. foreach (['pay', 'transactions', 'reports', 'stats', 'contacts', 'calls', 'messenger', 'edit-profile', 'settings', 'change-password'] as $cap) { Gate::define("org {$cap}", $makeGate("org {$cap}")); } // Admin capability gates — checked inside admin-context components. foreach (['reports', 'contacts', 'messenger', 'settings', 'change-password'] as $cap) { Gate::define("admin {$cap}", $makeGate("admin {$cap}")); } foreach ($granular as $resource => $verbs) { foreach ($verbs as $verb) { Gate::define("{$verb} {$resource}", $makeGate("{$verb} {$resource}")); } } // Regular users can always manage their own calls — no role/permission needed. // Ownership enforcement is done at the component level (query scoped to own calls). $userCallVerbs = ['create', 'update', 'publish', 'unpublish', 'delete']; foreach ($userCallVerbs as $verb) { Gate::define("{$verb} calls", function ($user) use ($verb, $makeGate) { $activeType = session('activeProfileType'); if ($activeType && class_basename($activeType) === 'User') { return true; } return $makeGate("{$verb} calls")($user); }); } // access {resource} — page-level gate used on routes. // 'view {resource}' grants read-only page entry; 'manage {resource}' grants full access. // Roles without any permission for a resource cannot reach the page. $accessResources = array_keys($granular); foreach ($accessResources as $resource) { Gate::define("access {$resource}", function ($user) use ($resource, $makeGate) { return $makeGate("manage {$resource}")($user) || $makeGate("view {$resource}")($user); }); } // Route-level gate for the scoped bank-manager "manage accounts" surface // (Profiles\Manage.php, non-central banks). Bank-scoped roles created via // Roles\Manage.php only ever carry 'org *' style capability permissions — // never the Admin-style 'manage accounts' permission the granular gates // above check for — so this checks bank-manager role membership directly, // the same convention getCanCreatePayments() already uses for banks. Gate::define('bank manage accounts', function ($user) { if (!($user instanceof \App\Models\User)) { $user = \Illuminate\Support\Facades\Auth::guard('web')->user(); if (!($user instanceof \App\Models\User)) { return false; } } $activeType = session('activeProfileType'); $activeId = session('activeProfileId'); if ($activeType !== \App\Models\Bank::class || !$activeId) { return false; } return $user->hasRole("Bank\\{$activeId}\\bank-manager"); }); } }