getHost(); $centralDomains = config('tenancy.central_domains', ['127.0.0.1', 'localhost', 'hrm.test']); if (in_array($host, $centralDomains, true) || filter_var($host, FILTER_VALIDATE_IP)) { $headerTenantId = $request->header('X-Tenant') ?? $request->header('X-Subdomain') ?? $request->header('X-Tenant-Id') ?? $request->subdomain ?? $request->tenant; if ($headerTenantId && class_exists('\App\Models\Tenant')) { $targetTenant = \App\Models\Tenant::find($headerTenantId); if (!$targetTenant) { $domainMatch = \Stancl\Tenancy\Database\Models\Domain::where('domain', $headerTenantId)->first(); $targetTenant = $domainMatch ? $domainMatch->tenant : null; } if ($targetTenant) { if (function_exists('tenancy') && tenancy()->initialized) { tenancy()->end(); } tenancy()->initialize($targetTenant); } } // Fallback: Auto-discover tenant from Sanctum Bearer token if X-Tenant header is not present if (!function_exists('tenant') || !tenant()) { $bearer = $request->bearerToken(); if ($bearer && str_contains($bearer, ':') && str_contains($bearer, '|')) { [$tokenTenantId, $cleanToken] = explode(':', $bearer, 2); $targetTenant = \App\Models\Tenant::find($tokenTenantId); if ($targetTenant) { if (function_exists('tenancy') && tenancy()->initialized) { tenancy()->end(); } tenancy()->initialize($targetTenant); // Strip tenant prefix from Authorization header for Sanctum authentication $request->headers->set('Authorization', 'Bearer ' . $cleanToken); $request->server->set('HTTP_AUTHORIZATION', 'Bearer ' . $cleanToken); $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer ' . $cleanToken; } } if ((!function_exists('tenant') || !tenant()) && $bearer && str_contains($bearer, '|')) { [$tokenId, $plainText] = explode('|', $bearer, 2); $tokenHash = hash('sha256', $plainText); // 1. O(1) Direct Lookup via Central Token Index Table try { $centralIndex = \Illuminate\Support\Facades\DB::connection('sqlite') ->table('central_token_indexes') ->where('token_id', $tokenId) ->first(); if ($centralIndex && class_exists('\App\Models\Tenant')) { $targetTenant = \App\Models\Tenant::find($centralIndex->tenant_id); if ($targetTenant) { if (function_exists('tenancy') && tenancy()->initialized) { tenancy()->end(); } tenancy()->initialize($targetTenant); } } } catch (\Throwable $e) {} // 2. Legacy Fallback auto-discovery if not present in central index if ((!function_exists('tenant') || !tenant()) && class_exists('\App\Models\Tenant')) { $tenants = \App\Models\Tenant::on('sqlite')->get(); foreach ($tenants as $t) { $db1 = database_path('tenant_' . $t->id . '.sqlite'); $db2 = database_path('tenant_' . $t->id); if (!file_exists($db1) && !file_exists($db2)) continue; try { $tokenFound = $t->run(function () use ($tokenId, $tokenHash) { return \Laravel\Sanctum\PersonalAccessToken::where('id', $tokenId) ->where('token', $tokenHash) ->first(); }); if ($tokenFound) { if (function_exists('tenancy') && tenancy()->initialized) { tenancy()->end(); } tenancy()->initialize($t); // Backport index into central table for O(1) subsequent calls try { \Illuminate\Support\Facades\DB::connection('sqlite')->table('central_token_indexes')->updateOrInsert( ['token_id' => $tokenId], ['tenant_id' => $t->id, 'created_at' => now(), 'updated_at' => now()] ); } catch (\Throwable $e2) {} break; } } catch (\Throwable $e) {} } } } } if (class_exists(\Spatie\Permission\PermissionRegistrar::class)) { app(\Spatie\Permission\PermissionRegistrar::class)->forgetCachedPermissions(); } return $next($request); } try { $response = app(InitializeTenancyByDomain::class)->handle($request, $next); if (function_exists('tenant') && tenant()) { try { $enabledModules = \App\Models\TenantModule::where('enabled', 1)->pluck('module_key')->toArray(); $inquiryKeys = []; foreach (\App\Constants\ModuleContract::MODULE_MAP as $inqKey => $cfg) { if (in_array($cfg['module_key'], $enabledModules)) { $inquiryKeys[] = $inqKey; } } $allowedPermissions = \App\Constants\ModuleContract::getPermissionsForInquiryKeys($inquiryKeys); foreach ($allowedPermissions as $perm) { $moduleName = 'General'; foreach (\App\Constants\ModuleContract::MODULE_MAP as $inqKey => $cfg) { if (in_array($perm, $cfg['permissions'], true)) { $moduleName = $cfg['label'] ?? $cfg['module_key']; break; } } $pRecord = \Spatie\Permission\Models\Permission::findOrCreate($perm, 'web'); if (empty($pRecord->module) || $pRecord->module !== $moduleName) { $pRecord->module = $moduleName; $pRecord->save(); } } } catch (\Exception $e) {} } return $response; } catch (\Stancl\Tenancy\Exceptions\TenantDatabaseDoesNotExistException $e) { $tenant = tenant(); if ($tenant) { $tenantId = $tenant->id; $dbFile = database_path('tenant_' . $tenantId); $dbFileSqlite = database_path('tenant_' . $tenantId . '.sqlite'); if (!file_exists($dbFile)) { touch($dbFile); } if (!file_exists($dbFileSqlite)) { touch($dbFileSqlite); } \Illuminate\Support\Facades\Artisan::call('migrate', [ '--database' => 'tenant', '--path' => [ 'database/migrations/tenant', 'database/migrations', ], '--force' => true, ]); return app(InitializeTenancyByDomain::class)->handle($request, $next); } abort(503, 'Tenant database setup is incomplete.'); } } }