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

@@ -85,18 +85,28 @@ $resolveLoginCandidates = static function (string $inputEmail) use ($userReadRep
}
$user = $userReadRepository->findByEmail($emailValue);
if (!$user || (int) ($user['active'] ?? 0) !== 1) {
return ['ok' => false];
$isKnownActiveUser = $user && (int) ($user['active'] ?? 0) === 1 && (int) ($user['id'] ?? 0) > 0;
$tenants = [];
$discovery = false;
if ($isKnownActiveUser) {
$userId = (int) $user['id'];
$tenants = $userTenantContextService->getAssignedActiveTenants($userId);
}
$userId = (int) ($user['id'] ?? 0);
if ($userId <= 0) {
return ['ok' => false];
}
$tenants = $userTenantContextService->getAssignedActiveTenants($userId);
// Fallback: domain-based SSO discovery for users not yet provisioned locally
if (!$tenants) {
return ['ok' => false];
$discoveryResults = $tenantSsoService->discoverTenantsByEmailDomain($emailValue);
if (!$discoveryResults) {
return ['ok' => false];
}
$discovery = true;
$tenants = array_map(static fn (array $r): array => $r['tenant'], $discoveryResults);
$discoveryMethodsByTenantId = [];
foreach ($discoveryResults as $r) {
$discoveryMethodsByTenantId[(int) $r['id']] = $r['methods'];
}
}
$candidates = [];
@@ -110,7 +120,9 @@ $resolveLoginCandidates = static function (string $inputEmail) use ($userReadRep
if ($tenantSlug === '') {
continue;
}
$methods = $tenantSsoService->resolveTenantLoginMethods($tenantId);
$methods = $discovery
? ($discoveryMethodsByTenantId[$tenantId] ?? ['local' => false, 'microsoft' => false, 'microsoft_reason' => '', 'ldap' => false, 'ldap_reason' => ''])
: $tenantSsoService->resolveTenantLoginMethods($tenantId);
$tenantUuid = (string) ($tenant['uuid'] ?? '');
$hasAvatar = $tenantUuid !== '' && $tenantAvatarService->hasAvatar($tenantUuid);
$candidate = [
@@ -135,9 +147,10 @@ $resolveLoginCandidates = static function (string $inputEmail) use ($userReadRep
return [
'ok' => true,
'email' => $emailValue,
'user' => $user,
'user' => $isKnownActiveUser ? $user : null,
'candidates' => $candidates,
'candidate_by_id' => $candidateById,
'discovery' => $discovery,
];
};