144 lines
5.2 KiB
PHP
144 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Inquiry;
|
|
use App\Jobs\ProvisionTenantJob;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use App\Models\Tenant;
|
|
use Tests\TestCase;
|
|
|
|
class TenantApiAuthTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_api_login_authenticates_against_isolated_tenant_database(): void
|
|
{
|
|
// 1. Create Tenant A
|
|
$inquiryA = Inquiry::create([
|
|
'company_name' => 'API Company A',
|
|
'contact_name' => 'API Admin A',
|
|
'email' => 'admin@api-comp-a.com',
|
|
'desired_subdomain' => 'api-comp-a',
|
|
'email_verified_at' => now(),
|
|
'status' => 'approved',
|
|
]);
|
|
(new ProvisionTenantJob($inquiryA))->handle();
|
|
|
|
// 2. Create Tenant B
|
|
$inquiryB = Inquiry::create([
|
|
'company_name' => 'API Company B',
|
|
'contact_name' => 'API Admin B',
|
|
'email' => 'admin@api-comp-b.com',
|
|
'desired_subdomain' => 'api-comp-b',
|
|
'email_verified_at' => now(),
|
|
'status' => 'approved',
|
|
]);
|
|
(new ProvisionTenantJob($inquiryB))->handle();
|
|
|
|
// 3. Login to Tenant A via API with X-Tenant header using Tenant A credentials -> SUCCESS
|
|
if (function_exists('tenancy') && tenancy()->initialized) {
|
|
tenancy()->end();
|
|
}
|
|
|
|
$responseA = $this->withHeaders(['X-Tenant' => 'api-comp-a'])
|
|
->postJson('/api/auth/login', [
|
|
'email' => 'admin@api-comp-a.com',
|
|
'password' => '12345678',
|
|
]);
|
|
|
|
$responseA->assertStatus(200)
|
|
->assertJsonPath('success', true);
|
|
|
|
// 4. Try logging into Tenant A via API using actual Tenant B credentials -> FAILS 401
|
|
if (function_exists('tenancy') && tenancy()->initialized) {
|
|
tenancy()->end();
|
|
}
|
|
$responseCross = $this->withHeaders(['X-Tenant' => 'api-comp-a'])
|
|
->postJson('/api/auth/login', [
|
|
'email' => 'admin@api-comp-b.com',
|
|
'password' => '12345678',
|
|
]);
|
|
|
|
$responseCross->assertStatus(401)
|
|
->assertJsonPath('success', false);
|
|
|
|
// 5. Try logging in without tenant context (no header, no subdomain, no domain host) -> FAILS 422
|
|
if (function_exists('tenancy') && tenancy()->initialized) {
|
|
tenancy()->end();
|
|
}
|
|
|
|
$responseNoTenant = $this->withHeaders(['X-Tenant' => ''])->postJson('/api/auth/login', [
|
|
'email' => 'admin@api-comp-a.com',
|
|
'password' => '12345678',
|
|
]);
|
|
|
|
$responseNoTenant->assertStatus(422)
|
|
->assertJsonPath('message', 'Tenant context is required.');
|
|
|
|
// 6. Verify API token is created inside Tenant A database
|
|
$tenantA = Tenant::find('api-comp-a');
|
|
$tenantA->run(function () {
|
|
$userA = \App\Models\User::where('email', 'admin@api-comp-a.com')->first();
|
|
$this->assertNotNull($userA);
|
|
$this->assertGreaterThan(0, $userA->tokens()->count());
|
|
});
|
|
}
|
|
|
|
public function test_host_based_mobile_login_and_authenticated_request_are_tenant_isolated(): void
|
|
{
|
|
$inquiryA = Inquiry::create([
|
|
'company_name' => 'Mobile Host Company A',
|
|
'contact_name' => 'Mobile Admin A',
|
|
'email' => 'admin@mobile-host-a.com',
|
|
'desired_subdomain' => 'mobile-host-a',
|
|
'email_verified_at' => now(),
|
|
'status' => 'approved',
|
|
]);
|
|
(new ProvisionTenantJob($inquiryA))->handle();
|
|
|
|
$inquiryB = Inquiry::create([
|
|
'company_name' => 'Mobile Host Company B',
|
|
'contact_name' => 'Mobile Admin B',
|
|
'email' => 'admin@mobile-host-b.com',
|
|
'desired_subdomain' => 'mobile-host-b',
|
|
'email_verified_at' => now(),
|
|
'status' => 'approved',
|
|
]);
|
|
(new ProvisionTenantJob($inquiryB))->handle();
|
|
|
|
tenancy()->end();
|
|
|
|
$domainHost = config('app.url_domain', parse_url(config('app.url'), PHP_URL_HOST) ?: 'localhost');
|
|
$hostA = "mobile-host-a.{$domainHost}";
|
|
|
|
$loginA = $this->withServerVariables(['HTTP_HOST' => $hostA])
|
|
->postJson("http://{$hostA}/api/auth/login", [
|
|
'email' => 'admin@mobile-host-a.com',
|
|
'password' => '12345678',
|
|
'device_name' => 'mobile-a',
|
|
]);
|
|
|
|
$loginA->assertStatus(200)->assertJsonPath('success', true);
|
|
$tokenA = $loginA->json('token');
|
|
$this->assertNotEmpty($tokenA);
|
|
|
|
$userA = $this->withServerVariables(['HTTP_HOST' => $hostA])
|
|
->withHeader('Authorization', "Bearer {$tokenA}")
|
|
->getJson("http://{$hostA}/api/user");
|
|
|
|
$userA->assertStatus(200)->assertJsonPath('user.email', 'admin@mobile-host-a.com');
|
|
|
|
tenancy()->end();
|
|
|
|
$crossLogin = $this->withServerVariables(['HTTP_HOST' => $hostA])
|
|
->postJson("http://{$hostA}/api/auth/login", [
|
|
'email' => 'admin@mobile-host-b.com',
|
|
'password' => '12345678',
|
|
'device_name' => 'mobile-cross-tenant',
|
|
]);
|
|
|
|
$crossLogin->assertStatus(401)->assertJsonPath('success', false);
|
|
}
|
|
}
|