tenantId)) { return ProviderResult::failed( ProviderResult::ERROR_INTERNAL, 'Invalid tenant context' ); } $identity = $context->userIdentity; if (empty($identity)) { return ProviderResult::failed( ProviderResult::ERROR_INVALID_CREDENTIALS, 'Identity is required' ); } // Fetch credentials (timing-safe: always compute hash) $storedCredential = $this->store->fetchByIdentifier($context->tenantId, $identity); // Always verify password to prevent timing attacks $dummyHash = '$2y$10$' . str_repeat('0', 53); $hashToVerify = $storedCredential['secret'] ?? $dummyHash; $isValid = $this->crypto->verifyPassword($secret, $hashToVerify); if (!$storedCredential || !$isValid) { return ProviderResult::failed( ProviderResult::ERROR_INVALID_CREDENTIALS, 'Invalid credentials' ); } // Check if password needs rehash if ($this->crypto->needsRehash($hashToVerify)) { $newHash = $this->crypto->hashPassword($secret); $this->store->updateSecret($context->tenantId, $identity, $newHash); } return ProviderResult::success([ 'identity' => $identity, 'provider' => $this->identifier(), ]); } // ========================================================================= // Credential Management // ========================================================================= /** * Set or update user credentials */ public function setCredential(string $tenantId, string $userId, string $password): bool { if (empty($tenantId)) { return false; } $hash = $this->crypto->hashPassword($password); // Check if credential exists, then update or create if ($this->store->exists($tenantId, $userId)) { return $this->store->updateSecret($tenantId, $userId, $hash); } return $this->store->create($tenantId, $userId, $hash); } /** * Verify a password without full authentication */ public function verifyPassword(string $tenantId, string $userId, string $password): bool { if (empty($tenantId)) { return false; } $storedCredential = $this->store->fetchByIdentifier($tenantId, $userId); if (!$storedCredential) { return false; } return $this->crypto->verifyPassword($password, $storedCredential['secret']); } /** * Check if user has credentials set */ public function hasCredentials(string $tenantId, string $userId): bool { if (empty($tenantId)) { return false; } return $this->store->exists($tenantId, $userId); } }