From 89a48a00f9a023c123dfb1c6b66cb116e0fb08a1 Mon Sep 17 00:00:00 2001 From: Daniel Seifert Date: Sat, 29 Oct 2022 00:19:34 +0200 Subject: [PATCH] enable key login in admin --- .../Controller/Admin/d3webauthnadminlogin.php | 167 ++++++++++++++++++ .../Controller/d3webauthnlogin.php | 1 - src/Application/Model/WebauthnConf.php | 1 + .../views/admin/de/d3webauthn_lang.php | 2 +- .../views/admin/en/d3webauthn_lang.php | 2 +- .../views/admin/tpl/d3webauthnlogin.tpl | 56 ++++++ .../Component/d3_webauthn_UserComponent.php | 59 ++++--- .../Admin/d3_LoginController_Webauthn.php | 92 ++++++++-- .../Application/Model/d3_User_Webauthn.php | 24 +++ src/metadata.php | 5 +- 10 files changed, 367 insertions(+), 42 deletions(-) create mode 100755 src/Application/Controller/Admin/d3webauthnadminlogin.php create mode 100644 src/Application/views/admin/tpl/d3webauthnlogin.tpl diff --git a/src/Application/Controller/Admin/d3webauthnadminlogin.php b/src/Application/Controller/Admin/d3webauthnadminlogin.php new file mode 100755 index 0000000..f7b3096 --- /dev/null +++ b/src/Application/Controller/Admin/d3webauthnadminlogin.php @@ -0,0 +1,167 @@ + + * @link http://www.oxidmodule.com + */ + +namespace D3\Webauthn\Application\Controller\Admin; + +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\Admin\AdminController; +use OxidEsales\Eshop\Application\Controller\Admin\LoginController; +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; + +class d3webauthnadminlogin extends AdminController +{ + protected $_sThisTemplate = 'd3webauthnadminlogin.tpl'; + + protected function _authorize() // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore + { + return true; + } + + /** + * @return null + * @throws DatabaseConnectionException + * @throws DatabaseErrorException + */ + public function render() + { + if (Registry::getSession()->hasVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH) || + false == Registry::getSession()->hasVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER) + ) { + $this->getUtils()->redirect('index.php?cl=admin_start'); + if (false == defined('OXID_PHP_UNIT')) { + // @codeCoverageIgnoreStart + exit; + // @codeCoverageIgnoreEnd + } + } + + $this->generateCredentialRequest(); + + //$this->addTplParam('navFormParams', Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_NAVFORMPARAMS)); + + return parent::render(); + } + + /** + * @throws DatabaseConnectionException + * @throws DatabaseErrorException + */ + public function generateCredentialRequest() + { + /** @var Webauthn $webauthn */ + $webauthn = oxNew(Webauthn::class); + $publicKeyCredentialRequestOptions = $webauthn->getRequestOptions(); + Registry::getSession()->setVariable(WebauthnConf::WEBAUTHN_LOGIN_OBJECT, $publicKeyCredentialRequestOptions); + $this->addTplParam('webauthn_publickey_login', $publicKeyCredentialRequestOptions); + $this->addTplParam('isAdmin', isAdmin()); + } + + 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)); + Registry::getSession()->setVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH, true); + + /** @var d3_webauthn_UserComponent $userCmp */ + $loginController = oxNew(LoginController::class); + return $loginController->checklogin(); + + //Registry::getSession()->setVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH, true); + } + + } catch (Exception $e) { + Registry::getUtilsView()->addErrorToDisplay($e->getMessage()); + + $user->logout(); + $this->getUtils()->redirect('index.php?cl=login'); + } + } + + /** + * @return Utils + */ + public function getUtils() + { + return Registry::getUtils(); + } + + public function getPreviousClass() + { + return Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTCLASS); + } + + public function previousClassIsOrderStep() + { + $sClassKey = Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTCLASS); + $resolvedClass = Registry::getControllerClassNameResolver()->getClassNameById($sClassKey); + $resolvedClass = $resolvedClass ? $resolvedClass : 'start'; + + /** @var FrontendController $oController */ + $oController = oxNew($resolvedClass); + return $oController->getIsOrderStep(); + } + + /** + * @return bool + */ + public function getIsOrderStep() + { + return $this->previousClassIsOrderStep(); + } + + /** + * Returns Bread Crumb - you are here page1/page2/page3... + * + * @return array + */ + public function getBreadCrumb() + { + $aPaths = []; + $aPath = []; + $iBaseLanguage = Registry::getLang()->getBaseLanguage(); + $aPath['title'] = Registry::getLang()->translateString('D3_WEBAUTHN_BREADCRUMB', $iBaseLanguage, false); + $aPath['link'] = $this->getLink(); + + $aPaths[] = $aPath; + + return $aPaths; + } +} \ No newline at end of file diff --git a/src/Application/Controller/d3webauthnlogin.php b/src/Application/Controller/d3webauthnlogin.php index 0cec098..06e8767 100755 --- a/src/Application/Controller/d3webauthnlogin.php +++ b/src/Application/Controller/d3webauthnlogin.php @@ -64,7 +64,6 @@ class d3webauthnlogin extends FrontendController */ public function generateCredentialRequest() { - $auth = Registry::getSession()->getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER); /** @var Webauthn $webauthn */ $webauthn = oxNew(Webauthn::class); $publicKeyCredentialRequestOptions = $webauthn->getRequestOptions(); diff --git a/src/Application/Model/WebauthnConf.php b/src/Application/Model/WebauthnConf.php index ced8ff3..4d6235a 100755 --- a/src/Application/Model/WebauthnConf.php +++ b/src/Application/Model/WebauthnConf.php @@ -20,6 +20,7 @@ class WebauthnConf 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_LOGINUSER = 'd3webauthnLoginUser'; // username entered in login form 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/views/admin/de/d3webauthn_lang.php b/src/Application/views/admin/de/d3webauthn_lang.php index a2320ff..384a90d 100755 --- a/src/Application/views/admin/de/d3webauthn_lang.php +++ b/src/Application/views/admin/de/d3webauthn_lang.php @@ -32,7 +32,7 @@ $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_WEBAUTHN_ERR_UNSECURECONNECTION' => 'Die Verwendung von Sicherheitsschlüsseln ist nur bei gesicherten oder lokalen Verbindungen (https) möglich.', '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.', diff --git a/src/Application/views/admin/en/d3webauthn_lang.php b/src/Application/views/admin/en/d3webauthn_lang.php index e48ee59..0158e2f 100755 --- a/src/Application/views/admin/en/d3webauthn_lang.php +++ b/src/Application/views/admin/en/d3webauthn_lang.php @@ -32,7 +32,7 @@ $aLang = [ 'D3_WEBAUTHN_REGISTEREDKEYS' => 'registered keys', - 'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'The use of security keys is only possible with secured connections (https).', + 'D3_WEBAUTHN_ERR_UNSECURECONNECTION' => 'The use of security keys is only possible with secured or local connections (https).', '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.', diff --git a/src/Application/views/admin/tpl/d3webauthnlogin.tpl b/src/Application/views/admin/tpl/d3webauthnlogin.tpl new file mode 100644 index 0000000..cdc7d3d --- /dev/null +++ b/src/Application/views/admin/tpl/d3webauthnlogin.tpl @@ -0,0 +1,56 @@ + + + + [{oxmultilang ident="LOGIN_TITLE"}] + + + + + + + + +
+ + + + [{include file="js_login.tpl"}] + +
+ + [{block name="admin_login_form"}] + [{$oViewConf->getHiddenSid()}] + + + + + [{if !empty($Errors.default)}] + [{include file="inc_error.tpl" Errorlist=$Errors.default}] + [{/if}] + +
+
+ [{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 *}] + + + + + [{oxstyle include=$oViewConf->getModuleUrl('d3webauthn', 'out/admin/src/css/d3webauthnlogin.css')}] + [{oxstyle}] + + [{/block}] +
+
+ +[{oxscript}] + + + + diff --git a/src/Modules/Application/Component/d3_webauthn_UserComponent.php b/src/Modules/Application/Component/d3_webauthn_UserComponent.php index 2874cb0..d00cd45 100755 --- a/src/Modules/Application/Component/d3_webauthn_UserComponent.php +++ b/src/Modules/Application/Component/d3_webauthn_UserComponent.php @@ -22,6 +22,7 @@ use D3\Webauthn\Application\Model\Exceptions\d3webauthnWrongAuthException; use D3\Webauthn\Application\Model\Webauthn; use D3\Webauthn\Modules\Application\Model\d3_User_Webauthn; use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Exception; use Doctrine\DBAL\Query\QueryBuilder; use OxidEsales\Eshop\Application\Model\User; use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; @@ -30,6 +31,8 @@ use OxidEsales\Eshop\Core\Session; use OxidEsales\Eshop\Core\UtilsView; use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory; use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; class d3_webauthn_UserComponent extends d3_webauthn_UserComponent_parent { @@ -41,26 +44,7 @@ class d3_webauthn_UserComponent extends d3_webauthn_UserComponent_parent public function login_noredirect() { $lgn_user = Registry::getRequest()->getRequestParameter('lgn_usr'); - $user = oxNew(User::class); - - /** @var QueryBuilder $qb */ - $qb = ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create(); - $qb->select('oxid') - ->from($user->getViewName()) - ->where( - $qb->expr()->and( - $qb->expr()->eq( - 'oxusername', - $qb->createNamedParameter($lgn_user) - ), - $qb->expr()->eq( - 'oxshopid', - $qb->createNamedParameter(Registry::getConfig()->getShopId()) - ) - ) - )->setMaxResults(1); - - $userId = $qb->execute()->fetchOne(); + $userId = $this->d3GetLoginUserId($lgn_user); if ($lgn_user && $userId) { $webauthn = $this->d3GetWebauthnObject(); @@ -218,4 +202,39 @@ class d3_webauthn_UserComponent extends d3_webauthn_UserComponent_parent { return Registry::getSession(); } + + /** + * @return string|null + * @throws \Doctrine\DBAL\Driver\Exception + * @throws Exception + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function d3GetLoginUserId($username): ?string + { + if (empty($username)) { + return null; + } + + $user = oxNew(User::class); + + /** @var QueryBuilder $qb */ + $qb = ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create(); + $qb->select('oxid') + ->from($user->getViewName()) + ->where( + $qb->expr()->and( + $qb->expr()->eq( + 'oxusername', + $qb->createNamedParameter($username) + ), + $qb->expr()->eq( + 'oxshopid', + $qb->createNamedParameter(Registry::getConfig()->getShopId()) + ) + ) + )->setMaxResults(1); + + return $qb->execute()->fetchOne(); + } } \ No newline at end of file diff --git a/src/Modules/Application/Controller/Admin/d3_LoginController_Webauthn.php b/src/Modules/Application/Controller/Admin/d3_LoginController_Webauthn.php index 5e1b569..f09fe96 100755 --- a/src/Modules/Application/Controller/Admin/d3_LoginController_Webauthn.php +++ b/src/Modules/Application/Controller/Admin/d3_LoginController_Webauthn.php @@ -16,16 +16,24 @@ namespace D3\Webauthn\Modules\Application\Controller\Admin; use D3\Webauthn\Application\Model\d3webauthn; +use D3\Webauthn\Application\Model\Webauthn; use D3\Webauthn\Application\Model\WebauthnConf; use D3\Webauthn\Application\Model\Exceptions\d3WebauthnExceptionAbstract; use D3\Webauthn\Application\Model\Exceptions\d3webauthnMissingPublicKeyCredentialRequestOptions; use D3\Webauthn\Application\Model\Exceptions\d3webauthnWrongAuthException; +use Doctrine\DBAL\Driver\Exception as DoctrineException; +use Doctrine\DBAL\Exception; +use Doctrine\DBAL\Query\QueryBuilder; use OxidEsales\Eshop\Application\Model\User; use OxidEsales\Eshop\Core\Exception\DatabaseConnectionException; use OxidEsales\Eshop\Core\Exception\DatabaseErrorException; use OxidEsales\Eshop\Core\Registry; use OxidEsales\Eshop\Core\Session; use OxidEsales\Eshop\Core\UtilsView; +use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory; +use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; class d3_LoginController_Webauthn extends d3_LoginController_Webauthn_parent { @@ -56,11 +64,11 @@ class d3_LoginController_Webauthn extends d3_LoginController_Webauthn_parent } /** - * @return d3webauthn + * @return Webauthn */ - public function d3GetWebauthnObject() + public function d3GetWebauthnObject(): Webauthn { - return oxNew(d3webauthn::class); + return oxNew(Webauthn::class); } /** @@ -85,33 +93,81 @@ class d3_LoginController_Webauthn extends d3_LoginController_Webauthn_parent */ public function checklogin() { - //$sWebauth = Registry::getRequest()->getRequestEscapedParameter('keyauth'); - $sWebauth = base64_decode(Registry::getRequest()->getRequestParameter('keyauth')); + $lgn_user = Registry::getRequest()->getRequestParameter('user'); + $userId = $this->d3GetLoginUserId($lgn_user); - $webauthn = $this->d3GetWebauthnObject(); - $webauthn->loadByUserId(Registry::getSession()->getVariable("auth")); + if ($lgn_user && $userId && false === Registry::getSession()->hasVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH)) { + $webauthn = $this->d3GetWebauthnObject(); - $return = 'login'; + if ($webauthn->isActive($userId) + && false == Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH) + ) { + Registry::getSession()->setVariable( + WebauthnConf::WEBAUTHN_SESSION_CURRENTCLASS, + $this->getClassKey() != 'd3webauthnadminlogin' ? $this->getClassKey() : 'admin_start'); + Registry::getSession()->setVariable(WebauthnConf::WEBAUTHN_SESSION_CURRENTUSER, $userId); + Registry::getSession()->setVariable(WebauthnConf::WEBAUTHN_SESSION_LOGINUSER, $lgn_user); - try { - if ($this->isNoWebauthnOrNoLogin($webauthn)) { - $return = parent::checklogin(); - } elseif ($this->hasValidWebauthn($sWebauth, $webauthn)) { - $this->d3GetSession()->setVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH, $sWebauth); - $return = "admin_start"; +/* + Registry::getSession()->setVariable( + WebauthnConf::WEBAUTHN_SESSION_NAVFORMPARAMS, + $this->getViewConfig()->getNavFormParams() + ); +*/ + //$oUser->d3templogout(); + + return "d3webauthnadminlogin"; } - } catch (d3webauthnExceptionAbstract $oEx) { - $this->d3GetUtilsView()->addErrorToDisplay($oEx); } - return $return; + return parent::checklogin(); + } + + /** + * @param $username + * @return string|null + * @throws DoctrineException + * @throws Exception + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + protected function d3GetLoginUserId($username): ?string + { + if (empty($username)) { + return null; + } + + $user = oxNew(User::class); + + /** @var QueryBuilder $qb */ + $qb = ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create(); + $qb->select('oxid') + ->from($user->getViewName()) + ->where( + $qb->expr()->and( + $qb->expr()->eq( + 'oxusername', + $qb->createNamedParameter($username) + ), + $qb->expr()->eq( + 'oxshopid', + $qb->createNamedParameter(Registry::getConfig()->getShopId()) + ), + $qb->expr()->eq( + 'oxrights', + $qb->createNamedParameter('malladmin') + ) + ) + )->setMaxResults(1); + + return $qb->execute()->fetchOne(); } /** * @param d3webauthn $webauthn * @return bool */ - public function isNoWebauthnOrNoLogin($webauthn) + public function d3IsNoWebauthnOrNoLogin($webauthn) { return false == $this->d3GetSession()->getVariable("auth") || false == $webauthn->isActive(); diff --git a/src/Modules/Application/Model/d3_User_Webauthn.php b/src/Modules/Application/Model/d3_User_Webauthn.php index 34ea7dc..0e942da 100755 --- a/src/Modules/Application/Model/d3_User_Webauthn.php +++ b/src/Modules/Application/Model/d3_User_Webauthn.php @@ -19,6 +19,7 @@ use D3\Webauthn\Application\Model\d3webauthn; use D3\Webauthn\Application\Model\WebauthnConf; use OxidEsales\Eshop\Core\Exception\StandardException; use OxidEsales\Eshop\Core\Registry; +use ReflectionClass; use Webauthn\PublicKeyCredentialUserEntity; class d3_User_Webauthn extends d3_User_Webauthn_parent @@ -78,4 +79,27 @@ class d3_User_Webauthn extends d3_User_Webauthn_parent throw oxNew(StandardException::class, 'can not create webauthn user entity from not loaded user'); } + + public function login($userName, $password, $setSessionCookie = false) + { + if (Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_AUTH)) { + $userName = $userName ?: Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_LOGINUSER); + $config = Registry::getConfig(); + $shopId = $config->getShopId(); + + /** private method is out of scope */ + $class = new ReflectionClass($this); + $method = $class->getMethod('loadAuthenticatedUser'); + $method->setAccessible(true); + $method->invokeArgs( + $this, + [ + Registry::getSession()->getVariable(WebauthnConf::WEBAUTHN_SESSION_LOGINUSER), + $shopId + ] + ); + } + + return parent::login($userName, $password, $setSessionCookie); + } } \ No newline at end of file diff --git a/src/metadata.php b/src/metadata.php index 2940562..3baa2c1 100755 --- a/src/metadata.php +++ b/src/metadata.php @@ -20,6 +20,7 @@ // https://docs.solokeys.io/solo/ use D3\Webauthn\Application\Controller\Admin\d3user_webauthn; +use D3\Webauthn\Application\Controller\Admin\d3webauthnadminlogin; use D3\Webauthn\Application\Controller\d3_account_webauthn; use D3\Webauthn\Application\Controller\d3webauthnlogin; use D3\Webauthn\Modules\Application\Component\d3_webauthn_UserComponent; @@ -66,7 +67,7 @@ $aModule = array( 'extend' => [ UserController::class => d3_webauthn_UserController::class, PaymentController::class => d3_webauthn_PaymentController::class, - OrderController::class => d3_webauthn_OrderController::class, + OrderController::class => d3_webauthn_OrderController::class, OxidModel\User::class => d3_User_Webauthn::class, StartController::class => d3_StartController_Webauthn::class, LoginController::class => d3_LoginController_Webauthn::class, @@ -76,11 +77,13 @@ $aModule = array( 'controllers' => [ 'd3user_webauthn' => d3user_webauthn::class, 'd3webauthnlogin' => d3webauthnlogin::class, + 'd3webauthnadminlogin' => d3webauthnadminlogin::class, 'd3_account_webauthn' => d3_account_webauthn::class ], 'templates' => [ 'd3user_webauthn.tpl' => 'd3/oxwebauthn/Application/views/admin/tpl/d3user_webauthn.tpl', 'd3webauthnlogin.tpl' => 'd3/oxwebauthn/Application/views/tpl/d3webauthnlogin.tpl', + 'd3webauthnadminlogin.tpl' => 'd3/oxwebauthn/Application/views/admin/tpl/d3webauthnlogin.tpl', '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',