diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index dbc30b01ba..2260281816 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -29,7 +29,6 @@ use Appwrite\Utopia\Database\Validator\Queries\Identities; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use MaxMind\Db\Reader; -use ParagonIE\ConstantTime\Base64UrlSafe; use Utopia\App; use Utopia\Audit\Audit as EventAudit; use Utopia\Config\Config; @@ -555,15 +554,16 @@ App::put('/v1/account/webauthn') $createdUser = Authorization::skip(fn () => $dbForProject->createDocument('users', $user)); // Create Authenticator - $dbForProject->createDocument('credentialSources', + $dbForProject->createDocument( + 'credentialSources', new Document( array_merge( - ['userInternalId' => $createdUser->getInternalId()], + ['userInternalId' => $createdUser->getInternalId()], $publicKeyCredentials->jsonSerialize() ) ) ); - + Authorization::skip(fn () => $dbForProject->deleteDocument('webauthnChallenges', $challengeId)); } catch (Duplicate) { throw new Exception(Exception::USER_ALREADY_EXISTS); @@ -4256,7 +4256,7 @@ App::delete('/v1/account/mfa/authenticator/webauthn') $dbForProject->purgeCachedDocument('users', $user->getId()); $queueForEvents->setParam('userId', $user->getId()); - + return $response->noContent(); } else { throw new Exception(Exception::USER_INVALID_TOKEN); @@ -4271,16 +4271,16 @@ App::delete('/v1/account/mfa/authenticator/webauthn') if ($challenge->isEmpty()) { throw new Exception(Exception::USER_INVALID_TOKEN); } - + $authenticators = array_filter(Webauthn::getAuthenticatorsFromUser($user), function ($auth) { return !empty($auth['verified']); }); - + $webauthn = new WebAuthn(); $relyingParty = $webauthn->createRelyingParty($project, $request); - + $responseJson = json_decode($challengeResponse, true); - + // Find authenticator used $authenticator = null; foreach ($authenticators as $auth) { @@ -4290,18 +4290,18 @@ App::delete('/v1/account/mfa/authenticator/webauthn') break; } } - + if ($authenticator === null) { throw new Exception(Exception::USER_AUTHENTICATOR_NOT_FOUND); } - + /** @var Document $authenticator */ - + // Check challenge try { $webauthn->verifyLoginChallenge( - challenge: $challenge->getArrayCopy(), - challengeResponse: $challengeResponse, + challenge: $challenge->getArrayCopy(), + challengeResponse: $challengeResponse, hostname: $request->gethostname(), timeout: Auth::TOKEN_EXPIRATION_WEBAUTHN, allowCredentials: $webauthn->getAllowedCredentials($user), @@ -4723,8 +4723,8 @@ App::put('/v1/account/mfa/challenge/webauthn') $publicKeyCredential = null; try { $publicKeyCredential = $webauthn->verifyLoginChallenge( - challenge: $challenge->getArrayCopy(), - challengeResponse: $challengeResponse, + challenge: $challenge->getArrayCopy(), + challengeResponse: $challengeResponse, hostname: $request->gethostname(), timeout: Auth::TOKEN_EXPIRATION_WEBAUTHN, allowCredentials: $webauthn->getAllowedCredentials($user), @@ -4738,7 +4738,7 @@ App::put('/v1/account/mfa/challenge/webauthn') // Update authenticator as counter has changed $dbForProject->updateDocument('authenticators', $authenticator->getId(), new Document([ 'data' => json_encode($publicKeyCredential) - ])); + ])); // Update Session $dbForProject->deleteDocument('challenges', $challengeId); diff --git a/src/Appwrite/Auth/MFA/Type/WebAuthn.php b/src/Appwrite/Auth/MFA/Type/WebAuthn.php index 6ca8573462..255775c41d 100644 --- a/src/Appwrite/Auth/MFA/Type/WebAuthn.php +++ b/src/Appwrite/Auth/MFA/Type/WebAuthn.php @@ -8,17 +8,17 @@ use Appwrite\Utopia\Request; use ParagonIE\ConstantTime\Base64UrlSafe; use Utopia\App; use Utopia\Database\Document; -use Webauthn\PublicKeyCredentialCreationOptions; -use Webauthn\PublicKeyCredentialRpEntity; -use Webauthn\PublicKeyCredentialUserEntity; use Webauthn\AttestationStatement\AttestationObjectLoader; use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AuthenticatorAssertionResponse; use Webauthn\AuthenticatorAssertionResponseValidator; use Webauthn\AuthenticatorAttestationResponseValidator; +use Webauthn\PublicKeyCredentialCreationOptions; use Webauthn\PublicKeyCredentialLoader; use Webauthn\PublicKeyCredentialRequestOptions; +use Webauthn\PublicKeyCredentialRpEntity; use Webauthn\PublicKeyCredentialSource; +use Webauthn\PublicKeyCredentialUserEntity; class WebAuthn extends Type { @@ -44,7 +44,7 @@ class WebAuthn extends Type /** * Create a new relying party entity, uses the platform if possible - * + * * @param Document $project * @param Request $request * @return PublicKeyCredentialRpEntity @@ -121,7 +121,7 @@ class WebAuthn extends Type /** * Create a new user entity from an Appwrite user document - * + * * @param Document $user * @return PublicKeyCredentialUserEntity */ @@ -138,7 +138,7 @@ class WebAuthn extends Type /** * Create a new register challenge - * + * * @param PublicKeyCredentialRpEntity $rpEntity * @param PublicKeyCredentialUserEntity $userEntity * @param int $timeout Timeout in seconds @@ -159,7 +159,7 @@ class WebAuthn extends Type /** * Create a new login challenge - * + * * @param PublicKeyCredentialRpEntity $rpEntity * @param PublicKeyCredentialSource[] $allowedCredentials * @param int $timeout Timeout in seconds @@ -187,7 +187,7 @@ class WebAuthn extends Type /** * Get all allowed credentials for a user - * + * * @param Document $user * @return PublicKeyCredentialSource[] */ @@ -216,10 +216,10 @@ class WebAuthn extends Type /** * Verify a register challenge - * + * * @param array $challenge The challenge data deserialized from the database * @param string $challengeResponse The challenge response from the client - * + * * @return PublicKeyCredentialSource * @throws \Throwable */ @@ -254,14 +254,14 @@ class WebAuthn extends Type /** * Verify a login challenge - * + * * @param array $challenge The challenge data deserialized from the database * @param string $challengeResponse The challenge response from the client * @param string $hostname The hostname of the request * @param int $timeout The timeout of the challenge, MUST be the same as the challenge * @param array $allowCredentials The allowed credentials for the challenge, MUST be the same as the challenge * @param PublicKeyCredentialSource $authenticatorPublicKey The public key of the authenticator - * + * * @throws \Throwable */ public function verifyLoginChallenge(array $challenge, string $challengeResponse, string $hostname, int $timeout, array $allowCredentials, PublicKeyCredentialRpEntity $rpEntity, PublicKeyCredentialSource $authenticatorPublicKey): PublicKeyCredentialSource @@ -292,7 +292,7 @@ class WebAuthn extends Type /** * Get all authenticators from a user - * + * * @param Document $user * @return Document[]|null * @throws Exception