328 lines
9.7 KiB
PHP
328 lines
9.7 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Kreait\Firebase\Auth;
|
||
|
|
|
||
|
|
use Beste\Json;
|
||
|
|
use DateInterval;
|
||
|
|
use GuzzleHttp\ClientInterface;
|
||
|
|
use Kreait\Firebase\Auth\CreateSessionCookie\GuzzleApiClientHandler;
|
||
|
|
use Kreait\Firebase\Auth\SignIn\GuzzleHandler;
|
||
|
|
use Kreait\Firebase\Exception\Auth\EmailNotFound;
|
||
|
|
use Kreait\Firebase\Exception\Auth\ExpiredOobCode;
|
||
|
|
use Kreait\Firebase\Exception\Auth\InvalidOobCode;
|
||
|
|
use Kreait\Firebase\Exception\Auth\OperationNotAllowed;
|
||
|
|
use Kreait\Firebase\Exception\Auth\UserDisabled;
|
||
|
|
use Kreait\Firebase\Exception\AuthApiExceptionConverter;
|
||
|
|
use Kreait\Firebase\Exception\AuthException;
|
||
|
|
use Kreait\Firebase\Request\CreateUser;
|
||
|
|
use Kreait\Firebase\Request\UpdateUser;
|
||
|
|
use Psr\Clock\ClockInterface;
|
||
|
|
use Psr\Http\Message\ResponseInterface;
|
||
|
|
use Stringable;
|
||
|
|
use Throwable;
|
||
|
|
|
||
|
|
use function array_filter;
|
||
|
|
use function array_map;
|
||
|
|
use function is_array;
|
||
|
|
use function str_contains;
|
||
|
|
use function time;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @internal
|
||
|
|
*/
|
||
|
|
class ApiClient
|
||
|
|
{
|
||
|
|
private readonly ProjectAwareAuthResourceUrlBuilder|TenantAwareAuthResourceUrlBuilder $awareAuthResourceUrlBuilder;
|
||
|
|
|
||
|
|
private readonly AuthResourceUrlBuilder $authResourceUrlBuilder;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param non-empty-string $projectId
|
||
|
|
* @param non-empty-string|null $tenantId
|
||
|
|
*/
|
||
|
|
public function __construct(
|
||
|
|
private readonly string $projectId,
|
||
|
|
private readonly ?string $tenantId,
|
||
|
|
private readonly ClientInterface $client,
|
||
|
|
private readonly GuzzleHandler $signInHandler,
|
||
|
|
private readonly ClockInterface $clock,
|
||
|
|
private readonly AuthApiExceptionConverter $errorHandler,
|
||
|
|
) {
|
||
|
|
$this->awareAuthResourceUrlBuilder = $tenantId !== null
|
||
|
|
? TenantAwareAuthResourceUrlBuilder::forProjectAndTenant($projectId, $tenantId)
|
||
|
|
: ProjectAwareAuthResourceUrlBuilder::forProject($projectId);
|
||
|
|
|
||
|
|
$this->authResourceUrlBuilder = AuthResourceUrlBuilder::create();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function createUser(CreateUser $request): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->authResourceUrlBuilder->getUrl('/accounts:signUp');
|
||
|
|
|
||
|
|
return $this->requestApi($url, Json::decode(Json::encode($request), true));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function updateUser(UpdateUser $request): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:update');
|
||
|
|
|
||
|
|
return $this->requestApi($url, Json::decode(Json::encode($request), true));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param array<non-empty-string, mixed> $claims
|
||
|
|
*
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function setCustomUserClaims(string $uid, array $claims): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:update');
|
||
|
|
|
||
|
|
return $this->requestApi($url, [
|
||
|
|
'localId' => $uid,
|
||
|
|
'customAttributes' => Json::encode((object) $claims),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Returns a user for the given email address.
|
||
|
|
*
|
||
|
|
* @throws AuthException
|
||
|
|
* @throws EmailNotFound
|
||
|
|
*/
|
||
|
|
public function getUserByEmail(string $email): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:lookup');
|
||
|
|
|
||
|
|
return $this->requestApi($url, ['email' => [$email]]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function getUserByPhoneNumber(string $phoneNumber): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:lookup');
|
||
|
|
|
||
|
|
return $this->requestApi($url, ['phoneNumber' => [$phoneNumber]]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function getUserByProviderUid(string $providerId, string $uid): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:lookup');
|
||
|
|
|
||
|
|
return $this->requestApi($url, ['federatedUserId' => [['providerId' => $providerId, 'rawId' => $uid]]]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function downloadAccount(?int $batchSize = null, ?string $nextPageToken = null): ResponseInterface
|
||
|
|
{
|
||
|
|
$batchSize ??= 1000;
|
||
|
|
|
||
|
|
$urlParams = array_filter([
|
||
|
|
'maxResults' => (string) $batchSize,
|
||
|
|
'nextPageToken' => (string) $nextPageToken,
|
||
|
|
], fn(string $value): bool => $value !== '');
|
||
|
|
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:batchGet', $urlParams);
|
||
|
|
|
||
|
|
return $this->requestApi($url);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function deleteUser(string $uid): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:delete');
|
||
|
|
|
||
|
|
return $this->requestApi($url, ['localId' => $uid]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param string[] $uids
|
||
|
|
*
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function deleteUsers(array $uids, bool $forceDeleteEnabledUsers): ResponseInterface
|
||
|
|
{
|
||
|
|
$data = [
|
||
|
|
'localIds' => $uids,
|
||
|
|
'force' => $forceDeleteEnabledUsers,
|
||
|
|
];
|
||
|
|
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:batchDelete');
|
||
|
|
|
||
|
|
return $this->requestApi($url, $data);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param string|list<non-empty-string> $uids
|
||
|
|
*
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function getAccountInfo(string|array $uids): ResponseInterface
|
||
|
|
{
|
||
|
|
if (!is_array($uids)) {
|
||
|
|
$uids = [$uids];
|
||
|
|
}
|
||
|
|
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:lookup');
|
||
|
|
|
||
|
|
return $this->requestApi($url, ['localId' => $uids]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function queryUsers(UserQuery $query): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:query');
|
||
|
|
|
||
|
|
return $this->requestApi($url, Json::decode(Json::encode($query), true));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
* @throws ExpiredOobCode
|
||
|
|
* @throws InvalidOobCode
|
||
|
|
* @throws OperationNotAllowed
|
||
|
|
*/
|
||
|
|
public function verifyPasswordResetCode(string $oobCode): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->authResourceUrlBuilder->getUrl('/accounts:resetPassword');
|
||
|
|
|
||
|
|
return $this->requestApi($url, ['oobCode' => $oobCode]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
* @throws ExpiredOobCode
|
||
|
|
* @throws InvalidOobCode
|
||
|
|
* @throws OperationNotAllowed
|
||
|
|
* @throws UserDisabled
|
||
|
|
*/
|
||
|
|
public function confirmPasswordReset(string $oobCode, string $newPassword): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->authResourceUrlBuilder->getUrl('/accounts:resetPassword');
|
||
|
|
|
||
|
|
return $this->requestApi($url, [
|
||
|
|
'oobCode' => $oobCode,
|
||
|
|
'newPassword' => $newPassword,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function revokeRefreshTokens(string $uid): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:update');
|
||
|
|
|
||
|
|
return $this->requestApi($url, [
|
||
|
|
'localId' => $uid,
|
||
|
|
'validSince' => (string) time(),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param list<Stringable|non-empty-string> $providers
|
||
|
|
*
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
public function unlinkProvider(string $uid, array $providers): ResponseInterface
|
||
|
|
{
|
||
|
|
$url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:update');
|
||
|
|
$providers = array_map('strval', $providers);
|
||
|
|
|
||
|
|
return $this->requestApi($url, [
|
||
|
|
'localId' => $uid,
|
||
|
|
'deleteProvider' => $providers,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function createSessionCookie(string $idToken, int|DateInterval $ttl): string
|
||
|
|
{
|
||
|
|
return (new GuzzleApiClientHandler($this->client, $this->projectId))
|
||
|
|
->handle(CreateSessionCookie::forIdToken($idToken, $this->tenantId, $ttl, $this->clock))
|
||
|
|
;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getEmailActionLink(string $type, string $email, ActionCodeSettings $actionCodeSettings, ?string $locale = null): string
|
||
|
|
{
|
||
|
|
return (new CreateActionLink\GuzzleApiClientHandler($this->client, $this->projectId))
|
||
|
|
->handle(CreateActionLink::new($type, $email, $actionCodeSettings, $this->tenantId, $locale))
|
||
|
|
;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* TODO: Make that this method can be emulated.
|
||
|
|
*/
|
||
|
|
public function sendEmailActionLink(string $type, string $email, ActionCodeSettings $actionCodeSettings, ?string $locale = null, ?string $idToken = null): void
|
||
|
|
{
|
||
|
|
$createAction = CreateActionLink::new($type, $email, $actionCodeSettings, $this->tenantId, $locale);
|
||
|
|
$sendAction = new SendActionLink($createAction, $locale);
|
||
|
|
|
||
|
|
if ($idToken !== null) {
|
||
|
|
$sendAction = $sendAction->withIdTokenString($idToken);
|
||
|
|
}
|
||
|
|
|
||
|
|
(new SendActionLink\GuzzleApiClientHandler($this->client, $this->projectId))->handle($sendAction);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* TODO: Make that this method can be emulated.
|
||
|
|
*/
|
||
|
|
public function handleSignIn(SignIn $action): SignInResult
|
||
|
|
{
|
||
|
|
if ($this->tenantId !== null) {
|
||
|
|
$action = $action->withTenantId($this->tenantId);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->signInHandler->handle($action);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @param array<string, mixed> $data
|
||
|
|
*
|
||
|
|
* @throws AuthException
|
||
|
|
*/
|
||
|
|
private function requestApi(string $uri, ?array $data = null): ResponseInterface
|
||
|
|
{
|
||
|
|
$options = [];
|
||
|
|
$method = 'GET';
|
||
|
|
|
||
|
|
if (!str_contains($uri, 'projects')) {
|
||
|
|
$data['targetProjectId'] = $this->projectId;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->tenantId !== null && !str_contains($uri, 'tenants')) {
|
||
|
|
$data['tenantId'] = $this->tenantId;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (is_array($data) && $data !== []) {
|
||
|
|
$method = 'POST';
|
||
|
|
$options['json'] = $data;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
return $this->client->request($method, $uri, $options);
|
||
|
|
} catch (Throwable $e) {
|
||
|
|
throw $this->errorHandler->convertException($e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|