diff --git a/src/Application/Model/Webauthn/PublicKeyCredentials.php b/src/Application/Model/Webauthn/PublicKeyCredentials.php
index 837dfbc..bdd2b4a 100644
--- a/src/Application/Model/Webauthn/PublicKeyCredentials.php
+++ b/src/Application/Model/Webauthn/PublicKeyCredentials.php
@@ -28,14 +28,43 @@ class PublicKeyCredentials extends BaseModel implements PublicKeyCredentialSourc
$qb->createNamedParameter(bin2hex($publicKeyCredentialId))
),
$qb->expr()->eq(
- 'shopid',
+ 'oxshopid',
$qb->createNamedParameter(Registry::getConfig()->getShopId())
)
)
);
$credential = $qb->execute()->fetchOne();
- return strlen($credential) ? hex2bin(unserialize($credential)) : null;
+ if (!strlen($credential)) {
+ return null;
+ }
+
+ $credential = unserialize(hex2bin($credential));
+
+ return $credential instanceof PublicKeyCredentialSource ? $credential : null;
+ }
+
+ public function getIdByCredentialId(string $publicKeyCredentialId): ?string
+ {
+ /** @var QueryBuilder $qb */
+ $qb = ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create();
+ $qb->select('oxid')
+ ->from($this->getViewName())
+ ->where(
+ $qb->expr()->and(
+ $qb->expr()->eq(
+ 'credid_hex',
+ $qb->createNamedParameter(bin2hex($publicKeyCredentialId))
+ ),
+ $qb->expr()->eq(
+ 'oxshopid',
+ $qb->createNamedParameter(Registry::getConfig()->getShopId())
+ )
+ )
+ );
+ $oxid = $qb->execute()->fetchOne();
+
+ return strlen($oxid) ? $oxid : null;
}
public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array
@@ -70,6 +99,10 @@ class PublicKeyCredentials extends BaseModel implements PublicKeyCredentialSourc
*/
public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void
{
+ // will saved on every successfully assertion, set id to prevent duplicated database entries
+ $id = $this->getIdByCredentialId($publicKeyCredentialSource->getPublicKeyCredentialId());
+ $this->setId($id);
+
$this->assign([
'oxshopid' => Registry::getConfig()->getShopId(),
'oxuserid' => $publicKeyCredentialSource->getUserHandle(),
diff --git a/src/Application/Model/Webauthn/Webauthn.php b/src/Application/Model/Webauthn/Webauthn.php
index 23d2278..c77a68e 100644
--- a/src/Application/Model/Webauthn/Webauthn.php
+++ b/src/Application/Model/Webauthn/Webauthn.php
@@ -10,6 +10,7 @@ use Nyholm\Psr7Server\ServerRequestCreator;
use OxidEsales\Eshop\Application\Model\User;
use OxidEsales\Eshop\Core\Registry;
use Webauthn\PublicKeyCredentialCreationOptions;
+use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialSource;
use Webauthn\Server;
@@ -17,7 +18,7 @@ use Webauthn\Server;
class Webauthn
{
public const SESSION_CREATIONS_OPTIONS = 'd3WebAuthnCreationOptions';
- public const SESSION_USERENTITY = 'd3WebAuthnUserEntity';
+ public const SESSION_ASSERTION_OPTIONS = 'd3WebAuthnAssertionOptions';
public function getCreationOptions()
{
@@ -26,8 +27,6 @@ class Webauthn
$user->load('oxdefaultadmin');
$userEntity = $user->d3GetWebauthnUserEntity();
- Registry::getSession()->setVariable(self::SESSION_USERENTITY, $userEntity);
-
$credentialSourceRepository = new PublicKeyCredentials();
$credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
$excludeCredentials = array_map(function (PublicKeyCredentialSource $credential) {
@@ -46,6 +45,35 @@ class Webauthn
return json_encode($publicKeyCredentialCreationOptions);
}
+ public function getRequestOptions()
+ {
+ /** @var d3_totp_user $user */
+ $user = oxNew(User::class);
+ $user->load('oxdefaultadmin');
+ $userEntity = $user->d3GetWebauthnUserEntity();
+
+ // Get the list of authenticators associated to the user
+ $credentialSourceRepository = oxNew(PublicKeyCredentials::class);
+ $credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
+
+ // Convert the Credential Sources into Public Key Credential Descriptors
+ $allowedCredentials = array_map(function (PublicKeyCredentialSource $credential) {
+ return $credential->getPublicKeyCredentialDescriptor();
+ }, $credentialSources);
+
+ $server = $this->getServer();
+
+ // We generate the set of options.
+ $publicKeyCredentialRequestOptions = $server->generatePublicKeyCredentialRequestOptions(
+ PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_PREFERRED, // Default value
+ $allowedCredentials
+ );
+
+ Registry::getSession()->setVariable(self::SESSION_ASSERTION_OPTIONS, $publicKeyCredentialRequestOptions);
+
+ return json_encode($publicKeyCredentialRequestOptions);
+ }
+
/**
* @return Server
*/
@@ -93,4 +121,46 @@ class Webauthn
die();
}
}
+
+ public function assertAuthn(string $response)
+ {
+ try {
+ $psr17Factory = new Psr17Factory();
+ $creator = new ServerRequestCreator(
+ $psr17Factory,
+ $psr17Factory,
+ $psr17Factory,
+ $psr17Factory
+ );
+ $serverRequest = $creator->fromGlobals();
+
+ /** @var d3_totp_user $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();
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Application/views/blocks/page/info/d3webauthn.tpl b/src/Application/views/blocks/page/info/d3webauthn.tpl
new file mode 100644
index 0000000..6f1fe2c
--- /dev/null
+++ b/src/Application/views/blocks/page/info/d3webauthn.tpl
@@ -0,0 +1,18 @@
+[{oxscript include=$oViewConf->getModuleUrl('d3totp', 'out/src/js/index.js')}]
+
+[{capture name="d3script"}]
+ var creationOptions = [{$requestOptions}];
+ requestCredentials(creationOptions);
+[{/capture}]
+[{oxscript add=$smarty.capture.d3script}]
+--A--
+
+--B--
+
+[{$smarty.block.parent}]
\ No newline at end of file
diff --git a/src/Application/views/blocks/page/shop/start_welcome_text.tpl b/src/Application/views/blocks/page/shop/start_welcome_text.tpl
index c473b35..95b5605 100644
--- a/src/Application/views/blocks/page/shop/start_welcome_text.tpl
+++ b/src/Application/views/blocks/page/shop/start_welcome_text.tpl
@@ -5,24 +5,6 @@
[{capture name="d3script"}]
var creationOptions = [{$creationOptions}];
createCredentials(creationOptions);
- [{***
- // Import the tool(s) ou need
- import {useRegistration} from '@web-auth/webauthn-helper';
-
- // We want to register new authenticators
- const register = useRegistration({
- actionUrl: '/api/register',
- optionsUrl: '/api/register/options'
- });
-
- register({
- username: 'FOO4',
- displayName: 'baR'
- })
- .then((response)=> console.log('Registration success'))
- .catch((error)=> console.log('Registration failure'))
- ;
- ***}]
[{/capture}]
[{oxscript add=$smarty.capture.d3script}]
--A--
diff --git a/src/IntelliSenseHelper.php b/src/IntelliSenseHelper.php
index bab0639..daaff78 100644
--- a/src/IntelliSenseHelper.php
+++ b/src/IntelliSenseHelper.php
@@ -24,8 +24,10 @@ namespace D3\Totp\Modules\Application\Component
namespace D3\Totp\Modules\Application\Controller
{
+ use OxidEsales\Eshop\Application\Controller\ContactController;
use OxidEsales\Eshop\Application\Controller\OrderController;
use OxidEsales\Eshop\Application\Controller\PaymentController;
+ use OxidEsales\Eshop\Application\Controller\StartController;
use OxidEsales\Eshop\Application\Controller\UserController;
class d3_totp_UserController_parent extends UserController
@@ -39,6 +41,14 @@ namespace D3\Totp\Modules\Application\Controller
class d3_totp_OrderController_parent extends OrderController
{
}
+
+ class d3_totp_StartController_parent extends StartController
+ {
+ }
+
+ class d3_totp_ContactController_parent extends ContactController
+ {
+ }
}
namespace D3\Totp\Modules\Application\Controller\Admin
diff --git a/src/Modules/Application/Controller/d3_totp_ContactController.php b/src/Modules/Application/Controller/d3_totp_ContactController.php
new file mode 100644
index 0000000..9afd5ba
--- /dev/null
+++ b/src/Modules/Application/Controller/d3_totp_ContactController.php
@@ -0,0 +1,23 @@
+addTplParam('requestOptions', $webAuthn->getRequestOptions());
+
+ return parent::render();
+ }
+
+ public function assertAuthn()
+ {
+ $webAuthn = oxNew(Webauthn::class);
+ $webAuthn->assertAuthn(Registry::getRequest()->getRequestEscapedParameter('credential'));
+ }
+}
\ No newline at end of file
diff --git a/src/metadata.php b/src/metadata.php
index 2ef7be6..e285866 100644
--- a/src/metadata.php
+++ b/src/metadata.php
@@ -19,6 +19,7 @@ use D3\Totp\Application\Controller\d3_account_totp;
use D3\Totp\Application\Controller\d3totplogin;
use D3\Totp\Modules\Application\Component\d3_totp_UserComponent;
use D3\Totp\Modules\Application\Controller\Admin\d3_totp_LoginController;
+use D3\Totp\Modules\Application\Controller\d3_totp_ContactController;
use D3\Totp\Modules\Application\Controller\d3_totp_OrderController;
use D3\Totp\Modules\Application\Controller\d3_totp_PaymentController;
use D3\Totp\Modules\Application\Controller\d3_totp_StartController;
@@ -28,6 +29,7 @@ use D3\Totp\Modules\Core\d3_totp_utils;
use D3\Totp\Setup as ModuleSetup;
use OxidEsales\Eshop\Application\Component\UserComponent;
use OxidEsales\Eshop\Application\Controller\Admin\LoginController;
+use OxidEsales\Eshop\Application\Controller\ContactController;
use OxidEsales\Eshop\Application\Controller\OrderController;
use OxidEsales\Eshop\Application\Controller\PaymentController;
use OxidEsales\Eshop\Application\Controller\StartController;
@@ -68,7 +70,8 @@ $aModule = [
LoginController::class => d3_totp_LoginController::class,
Utils::class => d3_totp_utils::class,
UserComponent::class => d3_totp_UserComponent::class,
- StartController::class => d3_totp_StartController::class
+ StartController::class => d3_totp_StartController::class,
+ ContactController::class => d3_totp_ContactController::class
],
'controllers' => [
'd3user_totp' => d3user_totp::class,
@@ -109,5 +112,10 @@ $aModule = [
'block' => 'start_welcome_text',
'file' => 'Application/views/blocks/page/shop/start_welcome_text.tpl',
],
+ [
+ 'template' => 'page/info/contact.tpl',
+ 'block' => 'd3webauthn',
+ 'file' => 'Application/views/blocks/page/info/d3webauthn.tpl',
+ ],
]
];
diff --git a/src/out/src/js/index.js b/src/out/src/js/index.js
index d62a4f8..7983019 100644
--- a/src/out/src/js/index.js
+++ b/src/out/src/js/index.js
@@ -19,7 +19,7 @@ const base64UrlDecode = (input) => {
return window.atob(input);
};
-const preparePublicKeyOptions = (publicKey) => {
+const prepareOptions = (publicKey) => {
"use strict";
//Convert challenge from Base64Url string to Uint8Array
publicKey.challenge = Uint8Array.from(
@@ -70,6 +70,7 @@ const preparePublicKeyOptions = (publicKey) => {
return publicKey;
};
+/** https://gist.github.com/jonleighton/958841 **/
function base64ArrayBuffer(arrayBuffer) {
"use strict";
var base64 = ''
@@ -126,7 +127,7 @@ function base64ArrayBuffer(arrayBuffer) {
const createCredentials = (publicKey) => {
"use strict";
- preparePublicKeyOptions(publicKey);
+ prepareOptions(publicKey);
navigator.credentials.create({publicKey: publicKey})
.then(function (newCredentialInfo) {
@@ -150,3 +151,35 @@ 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,
+ rawId: base64ArrayBuffer(authenticateInfo.rawId),
+ response: {
+ authenticatorData: base64ArrayBuffer(authenticateInfo.response.authenticatorData),
+ signature: base64ArrayBuffer(authenticateInfo.response.signature),
+ userHandle: authenticateInfo.response.userHandle,
+ clientDataJSON: base64ArrayBuffer(authenticateInfo.response.clientDataJSON)
+ },
+ 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.
+ });
+
+}
+