Files
HRM-System/app/Http/Middleware/IdentifyTenantIfNeeded.php

167 lines
8.4 KiB
PHP

<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Stancl\Tenancy\Middleware\InitializeTenancyByDomain;
class IdentifyTenantIfNeeded
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next)
{
$host = $request->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.');
}
}
}