167 lines
8.4 KiB
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.');
|
|
}
|
|
}
|
|
}
|