From a079006bf861f4d253dabed14391275dbfb0fca9 Mon Sep 17 00:00:00 2001 From: Alexis Saettler Date: Sun, 21 Jul 2024 22:53:04 +0200 Subject: [PATCH] feat: improve userless connection mode (#488) --- src/Events/WebauthnLoginData.php | 2 +- src/Services/Webauthn.php | 2 +- .../Webauthn/CredentialAssertionValidator.php | 18 ++++++++++++------ src/Services/Webauthn/CredentialValidator.php | 4 ++-- .../Webauthn/RequestOptionsFactory.php | 6 +++--- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Events/WebauthnLoginData.php b/src/Events/WebauthnLoginData.php index f4cf287..c553ba7 100644 --- a/src/Events/WebauthnLoginData.php +++ b/src/Events/WebauthnLoginData.php @@ -18,7 +18,7 @@ class WebauthnLoginData * @param PublicKeyCredentialRequestOptions $publicKey The authentication data. */ public function __construct( - public User $user, + public ?User $user, public PublicKeyCredentialRequestOptions $publicKey ) {} } diff --git a/src/Services/Webauthn.php b/src/Services/Webauthn.php index 1941514..c1e3ed2 100644 --- a/src/Services/Webauthn.php +++ b/src/Services/Webauthn.php @@ -102,7 +102,7 @@ public static function forgetAuthenticate(): void /** * Get publicKey data to prepare Webauthn login. */ - public static function prepareAssertion(User $user): PublicKeyCredentialRequestOptions + public static function prepareAssertion(?User $user): PublicKeyCredentialRequestOptions { return tap(app(RequestOptionsFactory::class)($user), function ($publicKey) use ($user) { WebauthnLoginData::dispatch($user, $publicKey); diff --git a/src/Services/Webauthn/CredentialAssertionValidator.php b/src/Services/Webauthn/CredentialAssertionValidator.php index 7d3970b..b82258f 100644 --- a/src/Services/Webauthn/CredentialAssertionValidator.php +++ b/src/Services/Webauthn/CredentialAssertionValidator.php @@ -30,7 +30,7 @@ public function __construct( * * @throws ResponseMismatchException */ - public function __invoke(User $user, array $data): bool + public function __invoke(?User $user, array $data): bool { // Load the data $content = json_encode($data, flags: JSON_THROW_ON_ERROR); @@ -51,11 +51,15 @@ public function __invoke(User $user, array $data): bool /** * Get public Key credential. */ - protected function pullPublicKey(User $user): PublicKeyCredentialRequestOptions + protected function pullPublicKey(?User $user): PublicKeyCredentialRequestOptions { try { $value = $this->cache->pull($this->cacheKey($user)); + if ($value === null && in_array(config('webauthn.userless'), ['required', 'preferred'], true)) { + $value = $this->cache->pull($this->cacheKey(null)); + } + return $this->loader->deserialize($value, PublicKeyCredentialRequestOptions::class, 'json'); } catch (\Exception $e) { app('webauthn.log')->debug('Webauthn publickKey deserialize error', ['exception' => $e]); @@ -79,14 +83,16 @@ protected function getResponse(PublicKeyCredential $publicKeyCredential): Authen /** * Get credential source from user and public key. */ - protected function getCredentialSource(User $user, PublicKeyCredential $publicKeyCredential) + protected function getCredentialSource(?User $user, PublicKeyCredential $publicKeyCredential) { $credentialId = $publicKeyCredential->rawId; - return (Webauthn::model())::where('user_id', $user->getAuthIdentifier()) - ->where(fn ($query) => $query->where('credentialId', Base64UrlSafe::encode($credentialId)) + return (Webauthn::model())::where( + fn ($query) => $query->where('credentialId', Base64UrlSafe::encode($credentialId)) ->orWhere('credentialId', Base64UrlSafe::encodeUnpadded($credentialId)) - ) + )->where( + fn ($query) => $user !== null ? $query->where('user_id', $user->getAuthIdentifier()) : $query + ) ->firstOrFail() ->publicKeyCredentialSource; } diff --git a/src/Services/Webauthn/CredentialValidator.php b/src/Services/Webauthn/CredentialValidator.php index 38e0901..6d7b411 100644 --- a/src/Services/Webauthn/CredentialValidator.php +++ b/src/Services/Webauthn/CredentialValidator.php @@ -21,13 +21,13 @@ public function __construct( /** * Returns the cache key to remember the challenge for the user. */ - protected function cacheKey(User $user): string + protected function cacheKey(?User $user): string { return implode( '|', [ self::CACHE_PUBLICKEY_REQUEST, - get_class($user).':'.$user->getAuthIdentifier(), + $user !== null ? get_class($user).':'.$user->getAuthIdentifier() : '', hash('sha512', $this->request->host().'|'.$this->request->ip()), ] ); diff --git a/src/Services/Webauthn/RequestOptionsFactory.php b/src/Services/Webauthn/RequestOptionsFactory.php index 61caa92..c36b271 100644 --- a/src/Services/Webauthn/RequestOptionsFactory.php +++ b/src/Services/Webauthn/RequestOptionsFactory.php @@ -31,7 +31,7 @@ public function __construct( /** * Create a new PublicKeyCredentialCreationOptions object. */ - public function __invoke(User $user): PublicKeyCredentialRequestOptions + public function __invoke(?User $user): PublicKeyCredentialRequestOptions { $publicKey = new PublicKeyCredentialRequestOptions( $this->getChallenge(), @@ -63,9 +63,9 @@ private static function getUserVerification(Config $config): ?string * * @return array */ - private function getAllowedCredentials(User $user): array + private function getAllowedCredentials(?User $user): array { - return CredentialRepository::getRegisteredKeys($user); + return $user !== null ? CredentialRepository::getRegisteredKeys($user) : []; } /**