feat(auth): add domain-based SSO discovery for unprovisioned users

Users who exist in Microsoft Entra ID or LDAP but have no local account
were blocked at the email-first login step because the system required a
local user record before showing SSO options. This adds a domain-based
fallback: when the email is unknown locally, the system checks tenant SSO
configurations for matching allowed_domains and presents SSO-only login
options, enabling the existing JIT provisioning to trigger on callback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 14:35:57 +02:00
parent cacd1fb6d1
commit 1c784efd5c
7 changed files with 325 additions and 11 deletions

View File

@@ -102,4 +102,36 @@ class TenantLdapAuthRepository implements TenantLdapAuthRepositoryInterface
return $result !== false;
}
public function findEnabledWithExplicitDomain(string $domain): array
{
$domain = strtolower(trim($domain));
if ($domain === '') {
return [];
}
$rows = DB::select(
"select tenant_id, allowed_domains from tenant_auth_ldap where enabled = 1 and allowed_domains is not null and allowed_domains != '' and FIND_IN_SET(?, allowed_domains) > 0",
$domain
);
if (!is_array($rows)) {
return [];
}
$result = [];
foreach ($rows as $row) {
$item = $this->unwrap($row);
if (!is_array($item)) {
continue;
}
$tenantId = (int) ($item['tenant_id'] ?? 0);
if ($tenantId <= 0) {
continue;
}
$result[] = ['tenant_id' => $tenantId, 'allowed_domains' => (string) ($item['allowed_domains'] ?? '')];
}
return $result;
}
}

View File

@@ -10,4 +10,7 @@ interface TenantLdapAuthRepositoryInterface
public function listByTenantIds(array $tenantIds): array;
public function upsertByTenantId(int $tenantId, array $data): bool;
/** @return list<array{tenant_id: int, allowed_domains: string}> Enabled configs with explicit domain match */
public function findEnabledWithExplicitDomain(string $domain): array;
}

View File

@@ -86,4 +86,36 @@ class TenantMicrosoftAuthRepository implements TenantMicrosoftAuthRepositoryInte
return $result !== false;
}
public function findEnabledWithExplicitDomain(string $domain): array
{
$domain = strtolower(trim($domain));
if ($domain === '') {
return [];
}
$rows = DB::select(
"select tenant_id, allowed_domains from tenant_auth_microsoft where enabled = 1 and allowed_domains is not null and allowed_domains != '' and FIND_IN_SET(?, allowed_domains) > 0",
$domain
);
if (!is_array($rows)) {
return [];
}
$result = [];
foreach ($rows as $row) {
$item = $this->unwrap($row);
if (!is_array($item)) {
continue;
}
$tenantId = (int) ($item['tenant_id'] ?? 0);
if ($tenantId <= 0) {
continue;
}
$result[] = ['tenant_id' => $tenantId, 'allowed_domains' => (string) ($item['allowed_domains'] ?? '')];
}
return $result;
}
}

View File

@@ -10,4 +10,7 @@ interface TenantMicrosoftAuthRepositoryInterface
public function listByTenantIds(array $tenantIds): array;
public function upsertByTenantId(int $tenantId, array $data): bool;
/** @return list<array{tenant_id: int, allowed_domains: string}> Enabled configs with explicit domain match */
public function findEnabledWithExplicitDomain(string $domain): array;
}