From 71a1f8e53ba9f653d206abeab5d3628d913a779e Mon Sep 17 00:00:00 2001 From: Daniel Seifert Date: Fri, 28 Oct 2022 00:45:32 +0200 Subject: [PATCH] add key assertion in frontend login, translate error messages --- .../Controller/d3webauthnlogin.php | 53 +++++++++-- .../Model/Credential/PublicKeyCredential.php | 23 ++--- src/Application/Model/Webauthn.php | 60 +++++-------- .../Webauthn/d3PublicKeyCredentialSource.php | 2 + src/Application/Model/WebauthnConf.php | 10 +-- src/Application/Model/WebauthnErrors.php | 44 +++++++++- src/Application/Model/d3webauthn.php | 4 +- .../views/admin/de/d3webauthn_lang.php | 30 ++----- .../views/admin/en/d3webauthn_lang.php | 29 ++---- src/Application/views/tpl/d3webauthnlogin.tpl | 88 +++---------------- src/Application/views/tpl/inc/js_login.tpl | 26 ++++++ .../Component/d3_webauthn_UserComponent.php | 7 +- src/Setup/Events.php | 1 - src/metadata.php | 1 + src/out/src/js/webauthn.js | 12 +-- 15 files changed, 180 insertions(+), 210 deletions(-) create mode 100644 src/Application/views/tpl/inc/js_login.tpl diff --git a/src/Application/Controller/d3webauthnlogin.php b/src/Application/Controller/d3webauthnlogin.php index f30dcce..0cec098 100755 --- a/src/Application/Controller/d3webauthnlogin.php +++ b/src/Application/Controller/d3webauthnlogin.php @@ -15,11 +15,17 @@ namespace D3\Webauthn\Application\Controller; -use D3\Webauthn\Application\Model\d3webauthn; +use D3\Webauthn\Application\Model\Webauthn; use D3\Webauthn\Application\Model\WebauthnConf; +use D3\Webauthn\Application\Model\WebauthnErrors; +use D3\Webauthn\Modules\Application\Component\d3_webauthn_UserComponent; +use D3\Webauthn\Modules\Application\Model\d3_User_Webauthn; +use Exception; use OxidEsales\Eshop\Application\Controller\FrontendController; +use OxidEsales\Eshop\Application\Model\User; use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; use OxidEsales\Eshop\Core\Exception\DatabaseErrorException; +use OxidEsales\Eshop\Core\Exception\StandardException; use OxidEsales\Eshop\Core\Registry; use OxidEsales\Eshop\Core\Utils; @@ -34,8 +40,6 @@ class d3webauthnlogin extends FrontendController */ public function render() { -dumpvar(__METHOD__.__LINE__); -die(); if (Registry::getSession()->hasVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH) || false == Registry::getSession()->hasVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER) ) { @@ -61,13 +65,44 @@ die(); public function generateCredentialRequest() { $auth = Registry::getSession()->getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER); - $webauthn = oxNew(d3webauthn::class); - $publicKeyCredentialRequestOptions = $webauthn->getCredentialRequestOptions($auth); + /** @var Webauthn $webauthn */ + $webauthn = oxNew(Webauthn::class); + $publicKeyCredentialRequestOptions = $webauthn->getRequestOptions(); + $this->addTplParam('webauthn_publickey_login', $publicKeyCredentialRequestOptions); + $this->addTplParam('isAdmin', isAdmin()); + } - $this->addTplParam( - 'webauthn_publickey_login', - $publicKeyCredentialRequestOptions - ); + public function assertAuthn() + { + /** @var d3_User_Webauthn $user */ + $user = oxNew(User::class); + + try { + if (strlen(Registry::getRequest()->getRequestEscapedParameter('error'))) { + $errors = oxNew(WebauthnErrors::class); + throw oxNew( + StandardException::class, + $errors->translateError(Registry::getRequest()->getRequestEscapedParameter('error')) + ); + } + + if (strlen(Registry::getRequest()->getRequestEscapedParameter('credential'))) { + $credential = Registry::getRequest()->getRequestEscapedParameter('credential'); + $webAuthn = oxNew(Webauthn::class); + $webAuthn->assertAuthn($credential); + $user->load(Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER)); + + /** @var d3_webauthn_UserComponent $userCmp */ + $userCmp = $this->getComponent('oxcmp_user'); + $userCmp->d3WebauthnRelogin($user, $credential); + } + + } catch (Exception $e) { + Registry::getUtilsView()->addErrorToDisplay($e->getMessage()); + + $user->logout(); + $this->getUtils()->redirect('index.php?cl=start'); + } } /** diff --git a/src/Application/Model/Credential/PublicKeyCredential.php b/src/Application/Model/Credential/PublicKeyCredential.php index 91434c8..8a941a5 100755 --- a/src/Application/Model/Credential/PublicKeyCredential.php +++ b/src/Application/Model/Credential/PublicKeyCredential.php @@ -17,6 +17,7 @@ namespace D3\Webauthn\Application\Model\Credential; +use DateTime; use Doctrine\DBAL\Query\QueryBuilder; use OxidEsales\Eshop\Core\Model\BaseModel; use OxidEsales\Eshop\Core\Registry; @@ -80,16 +81,6 @@ class PublicKeyCredential extends BaseModel { return unserialize(hex2bin($this->__get($this->_getFieldLongName('credential'))->rawValue)); } -/* - public function setPublicKey($publicKey) - { - $this->assign(['PublicKey' => $publicKey]); - } - - public function getPublicKey() - { - return $this->__get($this->_getFieldLongName('PublicKey'))->rawValue; - } /** * @param PublicKeyCredentialSource $publicKeyCredentialSource @@ -100,18 +91,16 @@ class PublicKeyCredential extends BaseModel { // will save on every successfully assertion, set id to prevent duplicated database entries $id = $this->getIdByCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId()); - $this->setId($id); + + if ($this->exists($id)) { + $this->load($id); + } $this->setShopId(Registry::getConfig()->getShopId()); $this->setUserId($publicKeyCredentialSource->getUserHandle()); $this->setCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId()); $this->setCredential($publicKeyCredentialSource); - $this->setName($keyName ?: $this->getName()); - -// ToDo: required?? - $this->assign([ - 'pubkey_hex' => bin2hex($publicKeyCredentialSource->getCredentialPublicKey()), - ]); + $this->setName($keyName ?: $this->getName() ?: (new DateTime())->format('Y-m-d H:i:s')); $this->save(); } diff --git a/src/Application/Model/Webauthn.php b/src/Application/Model/Webauthn.php index d325782..636c75c 100644 --- a/src/Application/Model/Webauthn.php +++ b/src/Application/Model/Webauthn.php @@ -75,8 +75,8 @@ class Webauthn $userEntity = $user->d3GetWebauthnUserEntity(); // Get the list of authenticators associated to the user - $credentialSourceRepository = oxNew(PublicKeyCredentials::class); - $credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity); + $credentialList = oxNew(PublicKeyCredentialList::class); + $credentialSources = $credentialList->findAllForUserEntity($userEntity); // Convert the Credential Sources into Public Key Credential Descriptors $allowedCredentials = array_map(function (PublicKeyCredentialSource $credential) { @@ -139,47 +139,30 @@ class Webauthn public function assertAuthn(string $response) { - try { - $psr17Factory = new Psr17Factory(); - $creator = new ServerRequestCreator( - $psr17Factory, - $psr17Factory, - $psr17Factory, - $psr17Factory - ); - $serverRequest = $creator->fromGlobals(); + $psr17Factory = new Psr17Factory(); + $creator = new ServerRequestCreator( + $psr17Factory, + $psr17Factory, + $psr17Factory, + $psr17Factory + ); + $serverRequest = $creator->fromGlobals(); - /** @var d3_User_Webauthn $user */ - $user = oxNew(User::class); - $user->load('oxdefaultadmin'); - $userEntity = $user->d3GetWebauthnUserEntity(); + /** @var d3_User_Webauthn $user */ + $user = oxNew(User::class); + $user->load('oxdefaultadmin'); + $userEntity = $user->d3GetWebauthnUserEntity(); - $publicKeySource = $this->getServer()->loadAndCheckAssertionResponse( - html_entity_decode($response), - Registry::getSession()->getVariable(self::SESSION_ASSERTION_OPTIONS), - $userEntity, - $serverRequest - ); -/* - dumpvar($publicKeySource); - dumpvar(serialize($publicKeySource)); - dumpvar(unserialize(serialize($publicKeySource))); - echo "
"; - dumpvar(bin2hex(serialize($publicKeySource))); - dumpvar(unserialize(hex2bin(bin2hex(serialize($publicKeySource))))); -*/ - - dumpvar('successfully'); - - } catch (\Exception $e) { - dumpvar($e->getMessage()); - dumpvar($e); - - die(); - } + $this->getServer()->loadAndCheckAssertionResponse( + html_entity_decode($response), + Registry::getSession()->getVariable(self::SESSION_ASSERTION_OPTIONS), + $userEntity, + $serverRequest + ); } /** + * @param $userId * @return bool */ public function isActive($userId): bool @@ -198,7 +181,6 @@ class Webauthn $user = oxNew(User::class); $user->load($userId); $entity = $user->d3GetWebauthnUserEntity(); - $credentionList = oxNew(PublicKeyCredentialList::class); $list = $credentionList->findAllForUserEntity($entity); diff --git a/src/Application/Model/Webauthn/d3PublicKeyCredentialSource.php b/src/Application/Model/Webauthn/d3PublicKeyCredentialSource.php index 0569c94..f3f2615 100755 --- a/src/Application/Model/Webauthn/d3PublicKeyCredentialSource.php +++ b/src/Application/Model/Webauthn/d3PublicKeyCredentialSource.php @@ -20,6 +20,8 @@ namespace D3\Webauthn\Application\Model\Webauthn; use D3\Webauthn\Application\Model\Credential\publicKeyCredential; use Webauthn\PublicKeyCredentialSource; +/** @deprecated */ + class d3PublicKeyCredentialSource extends PublicKeyCredentialSource { /** diff --git a/src/Application/Model/WebauthnConf.php b/src/Application/Model/WebauthnConf.php index 14d6dd2..ced8ff3 100755 --- a/src/Application/Model/WebauthnConf.php +++ b/src/Application/Model/WebauthnConf.php @@ -17,9 +17,9 @@ namespace D3\Webauthn\Application\Model; class WebauthnConf { - const WEBAUTHN_SESSION_AUTH = 'webauthn_auth'; - const WEBAUTHN_LOGIN_OBJECT = 'authnloginobject'; - const WEBAUTHN_SESSION_CURRENTUSER = 'd3webauthnCurrentUser'; - const WEBAUTHN_SESSION_CURRENTCLASS = 'd3webauthnCurrentClass'; - const WEBAUTHN_SESSION_NAVFORMPARAMS = 'd3webauthnNavFormParams'; + const WEBAUTHN_SESSION_AUTH = 'webauthn_auth'; // has valid webauthn, user is logged in completly + const WEBAUTHN_LOGIN_OBJECT = 'authnloginobject'; // webauthn register options, required for credential check + const WEBAUTHN_SESSION_CURRENTUSER = 'd3webauthnCurrentUser'; // oxid assigned to user from entered username + const WEBAUTHN_SESSION_CURRENTCLASS = 'd3webauthnCurrentClass'; // no usage + const WEBAUTHN_SESSION_NAVFORMPARAMS = 'd3webauthnNavFormParams'; // no usage } \ No newline at end of file diff --git a/src/Application/Model/WebauthnErrors.php b/src/Application/Model/WebauthnErrors.php index a76eae5..81fce30 100644 --- a/src/Application/Model/WebauthnErrors.php +++ b/src/Application/Model/WebauthnErrors.php @@ -2,15 +2,53 @@ namespace D3\Webauthn\Application\Model; +use OxidEsales\Eshop\Core\Registry; + class WebauthnErrors { + public const INVALIDSTATE = 'invalidstateerror'; + public const NOTALLWED = 'notallowederror'; + public const ABORT = 'aborterror'; + public const CONSTRAINT = 'constrainterror'; + public const NOTSUPPORTED = 'notsupporederror'; + public const UNKNOWN = 'unknownerror'; + public const NOPUBKEYSUPPORT= 'd3nopublickeycredentialsupportederror'; + + /** + * @see https://webidl.spec.whatwg.org/ + * @param $msg + * @return mixed|string + */ public function translateError($msg) { - switch ($msg) { - case 'InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable': - return 'A key from this token is already saved'; + $lang = Registry::getLang(); + + switch ($this->getErrIdFromMessage($msg)) { + case self::INVALIDSTATE: + return $lang->translateString('D3_WEBAUTHN_ERR_INVALIDSTATE', null, true); + case self::NOTALLWED: + return $lang->translateString('D3_WEBAUTHN_ERR_NOTALLOWED', null, true); + case self::ABORT: + return $lang->translateString('D3_WEBAUTHN_ERR_ABORT', null, true); + case self::CONSTRAINT: + return $lang->translateString('D3_WEBAUTHN_ERR_CONSTRAINT', null, true); + case self::NOTSUPPORTED: + return $lang->translateString('D3_WEBAUTHN_ERR_NOTSUPPORTED', null, true); + case self::UNKNOWN: + return $lang->translateString('D3_WEBAUTHN_ERR_UNKNOWN', null, true); + case self::NOPUBKEYSUPPORT: + return $lang->translateString('D3_WEBAUTHN_ERR_NOPUBKEYSUPPORT', null, true); } return $msg; } + + /** + * @param string $msg + * @return string + */ + public function getErrIdFromMessage(string $msg): string + { + return trim(strtolower(substr($msg, 0, strpos($msg, ':')))); + } } \ No newline at end of file diff --git a/src/Application/Model/d3webauthn.php b/src/Application/Model/d3webauthn.php index 9255c19..027c95e 100755 --- a/src/Application/Model/d3webauthn.php +++ b/src/Application/Model/d3webauthn.php @@ -146,7 +146,7 @@ class d3webauthn extends BaseModel $user = $this->getUser(); $userEntity = new d3PublicKeyCredentialUserEntity($user); -dumpvar($userEntity); + $allowedCredentials = []; $credentialSourceRepository = oxNew(d3PublicKeyCredentialSourceRepository::class); /** @var d3PublicKeyCredentialSource $credentialSource */ @@ -159,7 +159,7 @@ dumpvar($userEntity); PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // Default value $allowedCredentials ); -dumpvar($publicKeyCredentialRequestOptions); + $requestOptions = json_encode($publicKeyCredentialRequestOptions, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); Registry::getSession()->setVariable(WebauthnConf::WEBAUTHN_LOGIN_OBJECT, $publicKeyCredentialRequestOptions); diff --git a/src/Application/views/admin/de/d3webauthn_lang.php b/src/Application/views/admin/de/d3webauthn_lang.php index 658ad39..a2320ff 100755 --- a/src/Application/views/admin/de/d3webauthn_lang.php +++ b/src/Application/views/admin/de/d3webauthn_lang.php @@ -33,27 +33,11 @@ $aLang = [ 'D3_WEBAUTHN_REGISTEREDKEYS' => 'registrierte Schlüssel', 'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'Die Verwendung von Sicherheitsschlüsseln ist nur bei gesicherten Verbindungen (https) möglich.', -/* - 'D3_TOTP_QRCODE' => 'QR-Code', - 'D3_TOTP_QRCODE_HELP' => 'Scannen Sie diesen QR-Code mit Ihrer Authentisierungs-App, um dieses Benutzerkonto dort zu hinterlegen.', - 'D3_TOTP_SECRET' => 'QR-Code kann nicht gescannt werden?', - 'D3_TOTP_SECRET_HELP' => 'Setzen Sie keine App ein, die den QR-Code scannen kann, können Sie diese Zeichenkette auch in Ihr Authentisierungstool kopieren. Stellen Sie bitte die Passwortlänge auf 6 Zeichen und das Zeitinterval auf 30 Sekunden ein.', - 'D3_TOTP_CURROTP' => 'Bestätigung mit Einmalpasswort', - 'D3_TOTP_CURROTP_HELP' => 'Haben Sie dieses Kundenkonto in Ihrer Authentisierungs-App registriert, generieren Sie damit ein Einmalpasswort, tragen Sie es hier ein und senden das Formular direkt darauf hin ab.', - - 'D3_TOTP_REGISTEREXIST' => 'vorhandene Registrierung', - 'D3_TOTP_REGISTERDELETE' => 'Registrierung löschen', - 'D3_TOTP_REGISTERDELETE_DESC' => 'Um die Registrierung zu ändern, löschen Sie diese bitte vorerst. Sie können sofort im Anschluss eine neue Registrierung anlegen.
Wenn Sie die Registrierung löschen, ist das Konto nicht mehr durch die Zwei-Faktor-Authentisierung geschützt.', - 'D3_TOTP_REGISTERDELETED' => 'Die Registrierung wurde gelöscht.', - - 'D3_TOTP_BACKUPCODES' => 'Backupcodes', - 'D3_TOTP_BACKUPCODES_DESC' => 'Mit diesen Backupcodes können Sie sich anmelden, wenn die Generierung des Einmalpasswortes nicht möglich ist (z.B. Gerät verloren oder neu installiert). Sie können dann die Einstellungen zur Verwendung der 2-Faktor-Authentisierung ändern oder einen neuen Zugang erstellen. Speichern Sie sich diese Codes bitte in diesem Moment sicher ab. Nach Verlassen dieser Seite können diese Codes nicht erneut angezeigt werden.', - 'D3_TOTP_AVAILBACKUPCODECOUNT' => 'noch %1$s Backupcode(s) verfügbar', - 'D3_TOTP_AVAILBACKUPCODECOUNT_DESC' => 'Um neue Backupcodes zu erstellen, löschen Sie die bestehende Registrierung und legen diese bitte neu an.', - - 'D3_TOTP_SAVE' => 'Speichern', - - 'D3_TOTP_ERROR_UNVALID' => 'Das Einmalpasswort ist ungültig.', - 'D3_TOTP_ALREADY_EXIST' => 'Die Registrierung wurde schon gespeichert.', -*/ + 'D3_WEBAUTHN_ERR_INVALIDSTATE' => 'Der Schlüssel vom Token kann nicht oder nicht mehr verwendet werden. Möglicherweise wurde dieser in Ihrem Konto schon einmal gespeichert.', + 'D3_WEBAUTHN_ERR_NOTALLOWED' => 'Die Anfrage wurde vom Browser oder der Plattform nicht zugelassen. Möglicherweise fehlen Berechtigungen oder die Zeit ist abgelaufen.', + 'D3_WEBAUTHN_ERR_ABORT' => 'Die Aktion wurde vom Browser oder der Plattform abgebrochen.', + 'D3_WEBAUTHN_ERR_CONSTRAINT' => 'Die Aktion konnte vom authentisierenden Gerät nicht durchgeführt werden.', + 'D3_WEBAUTHN_ERR_NOTSUPPORTED' => 'Die Aktion wird nicht unterstützt.', + 'D3_WEBAUTHN_ERR_UNKNOWN' => 'Die Aktion wurde wegen eines unbekannten Fehlers abgebrochen.', + 'D3_WEBAUTHN_ERR_NOPUBKEYSUPPORT' => 'Ihr Browser unterstützt die Verwendung von Hardwareschlüsseln leider nicht.', ]; diff --git a/src/Application/views/admin/en/d3webauthn_lang.php b/src/Application/views/admin/en/d3webauthn_lang.php index 70e724b..e48ee59 100755 --- a/src/Application/views/admin/en/d3webauthn_lang.php +++ b/src/Application/views/admin/en/d3webauthn_lang.php @@ -33,27 +33,12 @@ $aLang = [ 'D3_WEBAUTHN_REGISTEREDKEYS' => 'registered keys', 'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'The use of security keys is only possible with secured connections (https).', -/* - 'D3_TOTP_QRCODE' => 'QR-Code', - 'D3_TOTP_QRCODE_HELP' => 'Scannen Sie diesen QR-Code mit Ihrer Authentisierungs-App, um dieses Benutzerkonto dort zu hinterlegen.', - 'D3_TOTP_SECRET' => 'QR-Code kann nicht gescannt werden?', - 'D3_TOTP_SECRET_HELP' => 'Setzen Sie keine App ein, die den QR-Code scannen kann, können Sie diese Zeichenkette auch in Ihr Authentisierungstool kopieren. Stellen Sie bitte die Passwortlänge auf 6 Zeichen und das Zeitinterval auf 30 Sekunden ein.', - 'D3_TOTP_CURROTP' => 'Bestätigung mit Einmalpasswort', - 'D3_TOTP_CURROTP_HELP' => 'Haben Sie dieses Kundenkonto in Ihrer Authentisierungs-App registriert, generieren Sie damit ein Einmalpasswort, tragen Sie es hier ein und senden das Formular direkt darauf hin ab.', + 'D3_WEBAUTHN_ERR_INVALIDSTATE' => 'The key from the token cannot be used or can no longer be used. It may have been stored in your account before.', + 'D3_WEBAUTHN_ERR_NOTALLOWED' => 'The request was not allowed by the browser or the platform. Possibly permissions are missing or the time has expired.', + 'D3_WEBAUTHN_ERR_ABORT' => 'The action was aborted by the browser or the platform.', + 'D3_WEBAUTHN_ERR_CONSTRAINT' => 'The action could not be performed by the authenticating device.', + 'D3_WEBAUTHN_ERR_NOTSUPPORTED' => 'The action is not supported.', + 'D3_WEBAUTHN_ERR_UNKNOWN' => 'The action was cancelled due to an unknown error.', + 'D3_WEBAUTHN_ERR_NOPUBKEYSUPPORT' => 'Unfortunately, your browser does not support the use of hardware keys.', - 'D3_TOTP_REGISTEREXIST' => 'vorhandene Registrierung', - 'D3_TOTP_REGISTERDELETE' => 'Registrierung löschen', - 'D3_TOTP_REGISTERDELETE_DESC' => 'Um die Registrierung zu ändern, löschen Sie diese bitte vorerst. Sie können sofort im Anschluss eine neue Registrierung anlegen.
Wenn Sie die Registrierung löschen, ist das Konto nicht mehr durch die Zwei-Faktor-Authentisierung geschützt.', - 'D3_TOTP_REGISTERDELETED' => 'Die Registrierung wurde gelöscht.', - - 'D3_TOTP_BACKUPCODES' => 'Backupcodes', - 'D3_TOTP_BACKUPCODES_DESC' => 'Mit diesen Backupcodes können Sie sich anmelden, wenn die Generierung des Einmalpasswortes nicht möglich ist (z.B. Gerät verloren oder neu installiert). Sie können dann die Einstellungen zur Verwendung der 2-Faktor-Authentisierung ändern oder einen neuen Zugang erstellen. Speichern Sie sich diese Codes bitte in diesem Moment sicher ab. Nach Verlassen dieser Seite können diese Codes nicht erneut angezeigt werden.', - 'D3_TOTP_AVAILBACKUPCODECOUNT' => 'noch %1$s Backupcode(s) verfügbar', - 'D3_TOTP_AVAILBACKUPCODECOUNT_DESC' => 'Um neue Backupcodes zu erstellen, löschen Sie die bestehende Registrierung und legen diese bitte neu an.', - - 'D3_TOTP_SAVE' => 'Speichern', - - 'D3_TOTP_ERROR_UNVALID' => 'Das Einmalpasswort ist ungültig.', - 'D3_TOTP_ALREADY_EXIST' => 'Die Registrierung wurde schon gespeichert.', -*/ ]; diff --git a/src/Application/views/tpl/d3webauthnlogin.tpl b/src/Application/views/tpl/d3webauthnlogin.tpl index b56f8b3..d53fb43 100755 --- a/src/Application/views/tpl/d3webauthnlogin.tpl +++ b/src/Application/views/tpl/d3webauthnlogin.tpl @@ -8,29 +8,19 @@
-
- [{$oViewConf->getHiddenSid()}] + [{if !empty($Errors.default)}] + [{include file="inc_error.tpl" Errorlist=$Errors.default}] + [{/if}] - - - - [{$navFormParams}] + [{include file="js_login.tpl"}] - [{if $Errors.default|@count}] - [{include file="inc_error.tpl" Errorlist=$Errors.default}] - [{/if}] - -
-
- [{include file=$oViewConf->getModulePath('d3webauthn', 'out/img/fingerprint.svg')}] -
-
[{oxmultilang ident="WEBAUTHN_INPUT_HELP"}]
+
+
+ [{include file=$oViewConf->getModulePath('d3webauthn', 'out/img/fingerprint.svg')}]
+
[{oxmultilang ident="WEBAUTHN_INPUT_HELP"}]
+
- [{* prevent cancel button (1st button) action when form is sent via Enter key *}] - - -
[{$oViewConf->getHiddenSid()}] @@ -38,71 +28,13 @@ [{$navFormParams}] - -
- [{if $webauthn_publickey_login}] - [{capture name="webauthn_login"}] - function arrayToBase64String(a) { - return btoa(String.fromCharCode(...a)); - } - - function base64url2base64(input) { - input = input - .replace(/=/g, "") - .replace(/-/g, '+') - .replace(/_/g, '/'); - - const pad = input.length % 4; - if(pad) { - if(pad === 1) { - throw new Error('InvalidLengthError: Input base64url string is the wrong length to determine padding'); - } - input += new Array(5-pad).join('='); - } - - return input; - } - - let publicKey = [{$webauthn_publickey_login}]; - - publicKey.challenge = Uint8Array.from(window.atob(base64url2base64(publicKey.challenge)), function(c){return c.charCodeAt(0);}); - if (publicKey.allowCredentials) { - publicKey.allowCredentials = publicKey.allowCredentials.map(function(data) { - data.id = Uint8Array.from(window.atob(base64url2base64(data.id)), function(c){return c.charCodeAt(0);}); - return data; - }); - } - - navigator.credentials.get({ 'publicKey': publicKey }).then(function(data){ - let publicKeyCredential = { - id: data.id, - type: data.type, - rawId: arrayToBase64String(new Uint8Array(data.rawId)), - response: { - authenticatorData: arrayToBase64String(new Uint8Array(data.response.authenticatorData)), - clientDataJSON: arrayToBase64String(new Uint8Array(data.response.clientDataJSON)), - signature: arrayToBase64String(new Uint8Array(data.response.signature)), - userHandle: data.response.userHandle ? arrayToBase64String(new Uint8Array(data.response.userHandle)) : null - } - }; - document.getElementById('keyauth').value = btoa(JSON.stringify(publicKeyCredential)); - document.getElementById('webauthnlogin').submit(); - }) - .catch(function(error){ - // alert('Open your browser console!'); - console.log('FAIL', error); - }); - [{/capture}] - [{oxscript add=$smarty.capture.webauthn_login}] - [{oxscript}] - [{/if}] - [{oxstyle include=$oViewConf->getModuleUrl('d3webauthn', 'out/flow/src/css/d3webauthnlogin.css')}] [{oxstyle}] diff --git a/src/Application/views/tpl/inc/js_login.tpl b/src/Application/views/tpl/inc/js_login.tpl new file mode 100644 index 0000000..8d02ec1 --- /dev/null +++ b/src/Application/views/tpl/inc/js_login.tpl @@ -0,0 +1,26 @@ +[{*** require creationOptions variable containing ... ***}] + +[{oxscript include=$oViewConf->getModuleUrl('d3webauthn', 'out/src/js/webauthn.js')}] + +[{capture name="d3script"}] + var requestOptions = [{$webauthn_publickey_login}]; + requestCredentials(requestOptions); +[{/capture}] +[{oxscript add=$smarty.capture.d3script}] + +[{if $isAdmin}] + [{assign var="action" value=$oViewConf->getSelfLink()}] + [{assign var="formNavParams" value=""}] +[{else}] + [{assign var="action" value=$oViewConf->getSelfActionLink()}] + [{assign var="formNavParams" value=""}] +[{/if}] + +
+ [{$oViewConf->getHiddenSid()}] + [{$formNavParams}] + + + + +
diff --git a/src/Modules/Application/Component/d3_webauthn_UserComponent.php b/src/Modules/Application/Component/d3_webauthn_UserComponent.php index 114c3a5..2874cb0 100755 --- a/src/Modules/Application/Component/d3_webauthn_UserComponent.php +++ b/src/Modules/Application/Component/d3_webauthn_UserComponent.php @@ -45,7 +45,7 @@ class d3_webauthn_UserComponent extends d3_webauthn_UserComponent_parent /** @var QueryBuilder $qb */ $qb = ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create(); - $qb->select('*') + $qb->select('oxid') ->from($user->getViewName()) ->where( $qb->expr()->and( @@ -62,15 +62,16 @@ class d3_webauthn_UserComponent extends d3_webauthn_UserComponent_parent $userId = $qb->execute()->fetchOne(); - if ($lgn_user) { + if ($lgn_user && $userId) { $webauthn = $this->d3GetWebauthnObject(); + if ($webauthn->isActive($userId) && false == Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH) ) { Registry::getSession()->setVariable( WebauthnConf::WEBAUTHN_SESSION_CURRENTCLASS, $this->getParent()->getClassKey() != 'd3webauthnlogin' ? $this->getParent()->getClassKey() : 'start'); - Registry::getSession()->setVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER, $user->getId()); + Registry::getSession()->setVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER, $userId); Registry::getSession()->setVariable( WebauthnConf::WEBAUTHN_SESSION_NAVFORMPARAMS, $this->getParent()->getViewConfig()->getNavFormParams() diff --git a/src/Setup/Events.php b/src/Setup/Events.php index 8436a02..f1ffbcc 100755 --- a/src/Setup/Events.php +++ b/src/Setup/Events.php @@ -35,7 +35,6 @@ class Events `OXSHOPID` int(11) NOT NULL, `NAME` varchar(100) NOT NULL, `CREDENTIALID` char(100) NOT NULL, - `PUBKEY_HEX` char(150) NOT NULL, `CREDENTIAL` varchar(2000) NOT NULL, `OXTIMESTAMP` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`OXID`), diff --git a/src/metadata.php b/src/metadata.php index 6d10819..2940562 100755 --- a/src/metadata.php +++ b/src/metadata.php @@ -84,6 +84,7 @@ $aModule = array( 'd3_account_webauthn.tpl' => 'd3/oxwebauthn/Application/views/tpl/d3_account_webauthn.tpl', 'js_create.tpl' => 'd3/oxwebauthn/Application/views/tpl/inc/js_create.tpl', + 'js_login.tpl' => 'd3/oxwebauthn/Application/views/tpl/inc/js_login.tpl', ], 'events' => [ 'onActivate' => '\D3\Webauthn\Setup\Events::onActivate', diff --git a/src/out/src/js/webauthn.js b/src/out/src/js/webauthn.js index 37084b8..de7e950 100644 --- a/src/out/src/js/webauthn.js +++ b/src/out/src/js/webauthn.js @@ -1,5 +1,6 @@ if (!window.PublicKeyCredential) { - console.error('no window pubkeycred available'); + document.getElementById('webauthn').error.value = 'd3NoPublicKeyCredentialSupportedError: aborting'; + document.getElementById('webauthn').submit(); } const base64UrlDecode = (input) => { @@ -152,13 +153,11 @@ const createCredentials = (publicKey) => { const requestCredentials = (publicKey) => { "use strict"; -console.log('--AB--'); prepareOptions(publicKey); navigator.credentials.get({publicKey: publicKey}) .then(function (authenticateInfo) { -console.log(authenticateInfo); // Send authenticate info to server for verification. var cred = { id: authenticateInfo.id, @@ -171,14 +170,11 @@ console.log(authenticateInfo); }, type: authenticateInfo.type }; -console.log(cred); document.getElementById('webauthn').credential.value = JSON.stringify(cred); document.getElementById('webauthn').submit(); }).catch(function (err) { - console.log('--2--'); - console.log('WebAuthn auth: ' + err); - // No acceptable authenticator or user refused consent. Handle appropriately. + document.getElementById('webauthn').error.value = err; + document.getElementById('webauthn').submit(); }); - }