Compare commits
2 Commits
master
...
dev_2.x_we
Author | SHA1 | Date | |
---|---|---|---|
b725333e86 | |||
911ff99c83 |
@ -43,7 +43,10 @@
|
|||||||
"oxid-esales/oxideshop-ce": "6.8.0 - 6.12",
|
"oxid-esales/oxideshop-ce": "6.8.0 - 6.12",
|
||||||
"spomky-labs/otphp": "^10.0 || ^11.0",
|
"spomky-labs/otphp": "^10.0 || ^11.0",
|
||||||
"bacon/bacon-qr-code": "^2.0",
|
"bacon/bacon-qr-code": "^2.0",
|
||||||
"laminas/laminas-math": "^3.2"
|
"laminas/laminas-math": "^3.2",
|
||||||
|
"web-auth/webauthn-lib": "^3.3",
|
||||||
|
"nyholm/psr7": "^1.5.1",
|
||||||
|
"nyholm/psr7-server": "^1.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"friendsofphp/php-cs-fixer": "^2.19",
|
"friendsofphp/php-cs-fixer": "^2.19",
|
||||||
|
117
src/Application/Model/Webauthn/PublicKeyCredentials.php
Normal file
117
src/Application/Model/Webauthn/PublicKeyCredentials.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace D3\Totp\Application\Model\Webauthn;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Query\QueryBuilder;
|
||||||
|
use OxidEsales\Eshop\Core\Model\BaseModel;
|
||||||
|
use OxidEsales\Eshop\Core\Registry;
|
||||||
|
use OxidEsales\EshopCommunity\Internal\Container\ContainerFactory;
|
||||||
|
use OxidEsales\EshopCommunity\Internal\Framework\Database\QueryBuilderFactoryInterface;
|
||||||
|
use Webauthn\PublicKeyCredentialSource;
|
||||||
|
use Webauthn\PublicKeyCredentialSourceRepository;
|
||||||
|
use Webauthn\PublicKeyCredentialUserEntity;
|
||||||
|
|
||||||
|
class PublicKeyCredentials extends BaseModel implements PublicKeyCredentialSourceRepository
|
||||||
|
{
|
||||||
|
protected $_sCoreTable = 'd3wa_usercredentials';
|
||||||
|
|
||||||
|
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource
|
||||||
|
{
|
||||||
|
/** @var QueryBuilder $qb */
|
||||||
|
$qb = ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create();
|
||||||
|
$qb->select('credential')
|
||||||
|
->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())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$credential = $qb->execute()->fetchOne();
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
/** @var QueryBuilder $qb */
|
||||||
|
$qb = ContainerFactory::getInstance()->getContainer()->get(QueryBuilderFactoryInterface::class)->create();
|
||||||
|
$qb->select('credential')
|
||||||
|
->from($this->getViewName())
|
||||||
|
->where(
|
||||||
|
$qb->expr()->and(
|
||||||
|
$qb->expr()->eq(
|
||||||
|
'oxuserid',
|
||||||
|
$qb->createNamedParameter($publicKeyCredentialUserEntity->getId())
|
||||||
|
),
|
||||||
|
$qb->expr()->eq(
|
||||||
|
'oxshopid',
|
||||||
|
$qb->createNamedParameter(Registry::getConfig()->getShopId())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// generate decoded credentials list
|
||||||
|
return array_map(function (array $fields) {
|
||||||
|
return unserialize(hex2bin($fields['credential']));
|
||||||
|
}, $qb->execute()->fetchAllAssociative());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param PublicKeyCredentialSource $publicKeyCredentialSource
|
||||||
|
* @return void
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
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(),
|
||||||
|
'credid_bin' => $publicKeyCredentialSource->getPublicKeyCredentialId(),
|
||||||
|
'credid_hex' => bin2hex($publicKeyCredentialSource->getPublicKeyCredentialId()),
|
||||||
|
'pubkey_bin' => $publicKeyCredentialSource->getCredentialPublicKey(),
|
||||||
|
'pubkey_hex' => bin2hex($publicKeyCredentialSource->getCredentialPublicKey()),
|
||||||
|
'credential' => bin2hex(serialize($publicKeyCredentialSource)),
|
||||||
|
]);
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
}
|
166
src/Application/Model/Webauthn/Webauthn.php
Normal file
166
src/Application/Model/Webauthn/Webauthn.php
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace D3\Totp\Application\Model\Webauthn;
|
||||||
|
|
||||||
|
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||||
|
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class Webauthn
|
||||||
|
{
|
||||||
|
public const SESSION_CREATIONS_OPTIONS = 'd3WebAuthnCreationOptions';
|
||||||
|
public const SESSION_ASSERTION_OPTIONS = 'd3WebAuthnAssertionOptions';
|
||||||
|
|
||||||
|
public function getCreationOptions()
|
||||||
|
{
|
||||||
|
/** @var d3_totp_user $user */
|
||||||
|
$user = oxNew(User::class);
|
||||||
|
$user->load('oxdefaultadmin');
|
||||||
|
$userEntity = $user->d3GetWebauthnUserEntity();
|
||||||
|
|
||||||
|
$credentialSourceRepository = new PublicKeyCredentials();
|
||||||
|
$credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity);
|
||||||
|
$excludeCredentials = array_map(function (PublicKeyCredentialSource $credential) {
|
||||||
|
return $credential->getPublicKeyCredentialDescriptor();
|
||||||
|
}, $credentialSources);
|
||||||
|
|
||||||
|
$server = $this->getServer();
|
||||||
|
$publicKeyCredentialCreationOptions = $server->generatePublicKeyCredentialCreationOptions(
|
||||||
|
$userEntity,
|
||||||
|
PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE,
|
||||||
|
$excludeCredentials
|
||||||
|
);
|
||||||
|
|
||||||
|
Registry::getSession()->setVariable(self::SESSION_CREATIONS_OPTIONS, $publicKeyCredentialCreationOptions);
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
public function getServer()
|
||||||
|
{
|
||||||
|
$rpEntity = new PublicKeyCredentialRpEntity(
|
||||||
|
Registry::getConfig()->getActiveShop()->getFieldData('oxname'),
|
||||||
|
preg_replace('/(^www\.)(.*)/mi', '$2', $_SERVER['HTTP_HOST'])
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Server($rpEntity, new PublicKeyCredentials());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveAuthn(string $credential)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$psr17Factory = new Psr17Factory();
|
||||||
|
$creator = new ServerRequestCreator(
|
||||||
|
$psr17Factory,
|
||||||
|
$psr17Factory,
|
||||||
|
$psr17Factory,
|
||||||
|
$psr17Factory
|
||||||
|
);
|
||||||
|
$serverRequest = $creator->fromGlobals();
|
||||||
|
|
||||||
|
$publicKeyCredentialSource = $this->getServer()->loadAndCheckAttestationResponse(
|
||||||
|
html_entity_decode($credential),
|
||||||
|
Registry::getSession()->getVariable(self::SESSION_CREATIONS_OPTIONS),
|
||||||
|
$serverRequest
|
||||||
|
);
|
||||||
|
|
||||||
|
dumpvar($publicKeyCredentialSource);
|
||||||
|
dumpvar(serialize($publicKeyCredentialSource));
|
||||||
|
dumpvar(unserialize(serialize($publicKeyCredentialSource)));
|
||||||
|
echo "<hr>";
|
||||||
|
dumpvar(bin2hex(serialize($publicKeyCredentialSource)));
|
||||||
|
dumpvar(unserialize(hex2bin(bin2hex(serialize($publicKeyCredentialSource)))));
|
||||||
|
|
||||||
|
$pkCredential = oxNew(PublicKeyCredentials::class);
|
||||||
|
$pkCredential->saveCredentialSource($publicKeyCredentialSource);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
dumpvar($e->getMessage());
|
||||||
|
dumpvar($e);
|
||||||
|
|
||||||
|
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 "<hr>";
|
||||||
|
dumpvar(bin2hex(serialize($publicKeySource)));
|
||||||
|
dumpvar(unserialize(hex2bin(bin2hex(serialize($publicKeySource)))));
|
||||||
|
*/
|
||||||
|
|
||||||
|
dumpvar('successfully');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
dumpvar($e->getMessage());
|
||||||
|
dumpvar($e);
|
||||||
|
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/Application/views/blocks/page/info/d3webauthn.tpl
Normal file
18
src/Application/views/blocks/page/info/d3webauthn.tpl
Normal file
@ -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--
|
||||||
|
<form id="webauthn" action="[{$oViewConf->getSelfActionLink()}]" method="post">
|
||||||
|
[{$oViewConf->getHiddenSid()}]
|
||||||
|
[{$oViewConf->getNavFormParams()}]
|
||||||
|
<input type="hidden" name="fnc" value="assertAuthn">
|
||||||
|
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
||||||
|
<input type="hidden" name="credential" value='credent'>
|
||||||
|
</form>
|
||||||
|
--B--
|
||||||
|
|
||||||
|
[{$smarty.block.parent}]
|
@ -0,0 +1,20 @@
|
|||||||
|
[{* from https://github.com/jcjones/webauthn.bin.coffee *}]
|
||||||
|
|
||||||
|
[{oxscript include=$oViewConf->getModuleUrl('d3totp', 'out/src/js/index.js')}]
|
||||||
|
|
||||||
|
[{capture name="d3script"}]
|
||||||
|
var creationOptions = [{$creationOptions}];
|
||||||
|
createCredentials(creationOptions);
|
||||||
|
[{/capture}]
|
||||||
|
[{oxscript add=$smarty.capture.d3script}]
|
||||||
|
--A--
|
||||||
|
<form id="webauthn" action="[{$oViewConf->getSelfActionLink()}]" method="post">
|
||||||
|
[{$oViewConf->getHiddenSid()}]
|
||||||
|
[{$oViewConf->getNavFormParams()}]
|
||||||
|
<input type="hidden" name="fnc" value="saveAuthn">
|
||||||
|
<input type="hidden" name="cl" value="[{$oViewConf->getActiveClassName()}]">
|
||||||
|
<input type="hidden" name="credential" value='credent'>
|
||||||
|
</form>
|
||||||
|
--B--
|
||||||
|
|
||||||
|
[{$smarty.block.parent}]
|
@ -24,8 +24,10 @@ namespace D3\Totp\Modules\Application\Component
|
|||||||
namespace D3\Totp\Modules\Application\Controller
|
namespace D3\Totp\Modules\Application\Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
use OxidEsales\Eshop\Application\Controller\ContactController;
|
||||||
use OxidEsales\Eshop\Application\Controller\OrderController;
|
use OxidEsales\Eshop\Application\Controller\OrderController;
|
||||||
use OxidEsales\Eshop\Application\Controller\PaymentController;
|
use OxidEsales\Eshop\Application\Controller\PaymentController;
|
||||||
|
use OxidEsales\Eshop\Application\Controller\StartController;
|
||||||
use OxidEsales\Eshop\Application\Controller\UserController;
|
use OxidEsales\Eshop\Application\Controller\UserController;
|
||||||
|
|
||||||
class d3_totp_UserController_parent extends 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_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
|
namespace D3\Totp\Modules\Application\Controller\Admin
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace D3\Totp\Modules\Application\Controller;
|
||||||
|
|
||||||
|
use D3\Totp\Application\Model\Webauthn\Webauthn;
|
||||||
|
use OxidEsales\Eshop\Core\Registry;
|
||||||
|
|
||||||
|
class d3_totp_ContactController extends d3_totp_ContactController_parent
|
||||||
|
{
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$webAuthn = oxNew(Webauthn::class);
|
||||||
|
$this->addTplParam('requestOptions', $webAuthn->getRequestOptions());
|
||||||
|
|
||||||
|
return parent::render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function assertAuthn()
|
||||||
|
{
|
||||||
|
$webAuthn = oxNew(Webauthn::class);
|
||||||
|
$webAuthn->assertAuthn(Registry::getRequest()->getRequestEscapedParameter('credential'));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace D3\Totp\Modules\Application\Controller;
|
||||||
|
|
||||||
|
use D3\Totp\Application\Model\Webauthn\Webauthn;
|
||||||
|
use OxidEsales\Eshop\Core\Registry;
|
||||||
|
|
||||||
|
class d3_totp_StartController extends d3_totp_StartController_parent
|
||||||
|
{
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$webAuthn = oxNew(Webauthn::class);
|
||||||
|
$this->addTplParam('creationOptions', $webAuthn->getCreationOptions());
|
||||||
|
|
||||||
|
return parent::render();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveAuthn()
|
||||||
|
{
|
||||||
|
$webAuthn = oxNew(Webauthn::class);
|
||||||
|
$webAuthn->saveAuthn(Registry::getRequest()->getRequestEscapedParameter('credential'));
|
||||||
|
}
|
||||||
|
}
|
@ -16,8 +16,10 @@ declare(strict_types=1);
|
|||||||
namespace D3\Totp\Modules\Application\Model;
|
namespace D3\Totp\Modules\Application\Model;
|
||||||
|
|
||||||
use D3\Totp\Application\Model\d3totp;
|
use D3\Totp\Application\Model\d3totp;
|
||||||
|
use OxidEsales\Eshop\Core\Exception\StandardException;
|
||||||
use OxidEsales\Eshop\Core\Registry;
|
use OxidEsales\Eshop\Core\Registry;
|
||||||
use OxidEsales\Eshop\Core\Session;
|
use OxidEsales\Eshop\Core\Session;
|
||||||
|
use Webauthn\PublicKeyCredentialUserEntity;
|
||||||
|
|
||||||
class d3_totp_user extends d3_totp_user_parent
|
class d3_totp_user extends d3_totp_user_parent
|
||||||
{
|
{
|
||||||
@ -45,4 +47,20 @@ class d3_totp_user extends d3_totp_user_parent
|
|||||||
{
|
{
|
||||||
return Registry::getSession();
|
return Registry::getSession();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* @return PublicKeyCredentialUserEntity
|
||||||
|
*/
|
||||||
|
public function d3GetWebauthnUserEntity(): PublicKeyCredentialUserEntity
|
||||||
|
{
|
||||||
|
if ($this->isLoaded()) {
|
||||||
|
return oxNew(PublicKeyCredentialUserEntity::class,
|
||||||
|
$this->getFieldData('oxusername'),
|
||||||
|
$this->getId(),
|
||||||
|
$this->getFieldData('oxfname') . ' ' . $this->getFieldData('oxlname')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw oxNew(StandardException::class, 'can not create webauthn user entity from not loaded user');
|
||||||
|
}
|
||||||
|
}
|
@ -19,16 +19,20 @@ use D3\Totp\Application\Controller\d3_account_totp;
|
|||||||
use D3\Totp\Application\Controller\d3totplogin;
|
use D3\Totp\Application\Controller\d3totplogin;
|
||||||
use D3\Totp\Modules\Application\Component\d3_totp_UserComponent;
|
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\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_OrderController;
|
||||||
use D3\Totp\Modules\Application\Controller\d3_totp_PaymentController;
|
use D3\Totp\Modules\Application\Controller\d3_totp_PaymentController;
|
||||||
|
use D3\Totp\Modules\Application\Controller\d3_totp_StartController;
|
||||||
use D3\Totp\Modules\Application\Controller\d3_totp_UserController;
|
use D3\Totp\Modules\Application\Controller\d3_totp_UserController;
|
||||||
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
use D3\Totp\Modules\Application\Model\d3_totp_user;
|
||||||
use D3\Totp\Modules\Core\d3_totp_utils;
|
use D3\Totp\Modules\Core\d3_totp_utils;
|
||||||
use D3\Totp\Setup as ModuleSetup;
|
use D3\Totp\Setup as ModuleSetup;
|
||||||
use OxidEsales\Eshop\Application\Component\UserComponent;
|
use OxidEsales\Eshop\Application\Component\UserComponent;
|
||||||
use OxidEsales\Eshop\Application\Controller\Admin\LoginController;
|
use OxidEsales\Eshop\Application\Controller\Admin\LoginController;
|
||||||
|
use OxidEsales\Eshop\Application\Controller\ContactController;
|
||||||
use OxidEsales\Eshop\Application\Controller\OrderController;
|
use OxidEsales\Eshop\Application\Controller\OrderController;
|
||||||
use OxidEsales\Eshop\Application\Controller\PaymentController;
|
use OxidEsales\Eshop\Application\Controller\PaymentController;
|
||||||
|
use OxidEsales\Eshop\Application\Controller\StartController;
|
||||||
use OxidEsales\Eshop\Application\Controller\UserController;
|
use OxidEsales\Eshop\Application\Controller\UserController;
|
||||||
use OxidEsales\Eshop\Core\Utils;
|
use OxidEsales\Eshop\Core\Utils;
|
||||||
use OxidEsales\Eshop\Application\Model as OxidModel;
|
use OxidEsales\Eshop\Application\Model as OxidModel;
|
||||||
@ -66,6 +70,8 @@ $aModule = [
|
|||||||
LoginController::class => d3_totp_LoginController::class,
|
LoginController::class => d3_totp_LoginController::class,
|
||||||
Utils::class => d3_totp_utils::class,
|
Utils::class => d3_totp_utils::class,
|
||||||
UserComponent::class => d3_totp_UserComponent::class,
|
UserComponent::class => d3_totp_UserComponent::class,
|
||||||
|
StartController::class => d3_totp_StartController::class,
|
||||||
|
ContactController::class => d3_totp_ContactController::class
|
||||||
],
|
],
|
||||||
'controllers' => [
|
'controllers' => [
|
||||||
'd3user_totp' => d3user_totp::class,
|
'd3user_totp' => d3user_totp::class,
|
||||||
@ -101,5 +107,15 @@ $aModule = [
|
|||||||
'block' => 'account_menu',
|
'block' => 'account_menu',
|
||||||
'file' => 'Application/views/blocks/page/account/inc/account_menu.tpl',
|
'file' => 'Application/views/blocks/page/account/inc/account_menu.tpl',
|
||||||
],
|
],
|
||||||
],
|
[
|
||||||
|
'template' => 'page/shop/start.tpl',
|
||||||
|
'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',
|
||||||
|
],
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
185
src/out/src/js/index.js
Normal file
185
src/out/src/js/index.js
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
if (!window.PublicKeyCredential) {
|
||||||
|
console.error('no window pubkeycred available');
|
||||||
|
}
|
||||||
|
|
||||||
|
const base64UrlDecode = (input) => {
|
||||||
|
"use strict";
|
||||||
|
input = input
|
||||||
|
.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 window.atob(input);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prepareOptions = (publicKey) => {
|
||||||
|
"use strict";
|
||||||
|
//Convert challenge from Base64Url string to Uint8Array
|
||||||
|
publicKey.challenge = Uint8Array.from(
|
||||||
|
base64UrlDecode(publicKey.challenge),
|
||||||
|
c => c.charCodeAt(0)
|
||||||
|
);
|
||||||
|
|
||||||
|
//Convert the user ID from Base64 string to Uint8Array
|
||||||
|
if (publicKey.user !== undefined) {
|
||||||
|
publicKey.user = {
|
||||||
|
...publicKey.user,
|
||||||
|
id: Uint8Array.from(
|
||||||
|
window.atob(publicKey.user.id),
|
||||||
|
c => c.charCodeAt(0)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//If excludeCredentials is defined, we convert all IDs to Uint8Array
|
||||||
|
if (publicKey.excludeCredentials !== undefined) {
|
||||||
|
publicKey.excludeCredentials = publicKey.excludeCredentials.map(
|
||||||
|
data => {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
id: Uint8Array.from(
|
||||||
|
base64UrlDecode(data.id),
|
||||||
|
c => c.charCodeAt(0)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (publicKey.allowCredentials !== undefined) {
|
||||||
|
publicKey.allowCredentials = publicKey.allowCredentials.map(
|
||||||
|
data => {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
id: Uint8Array.from(
|
||||||
|
base64UrlDecode(data.id),
|
||||||
|
c => c.charCodeAt(0)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return publicKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** https://gist.github.com/jonleighton/958841 **/
|
||||||
|
function base64ArrayBuffer(arrayBuffer) {
|
||||||
|
"use strict";
|
||||||
|
var base64 = ''
|
||||||
|
var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||||
|
|
||||||
|
var bytes = new Uint8Array(arrayBuffer)
|
||||||
|
var byteLength = bytes.byteLength
|
||||||
|
var byteRemainder = byteLength % 3
|
||||||
|
var mainLength = byteLength - byteRemainder
|
||||||
|
|
||||||
|
var a, b, c, d
|
||||||
|
var chunk
|
||||||
|
|
||||||
|
// Main loop deals with bytes in chunks of 3
|
||||||
|
for (var i = 0; i < mainLength; i = i + 3) {
|
||||||
|
// Combine the three bytes into a single integer
|
||||||
|
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]
|
||||||
|
|
||||||
|
// Use bitmasks to extract 6-bit segments from the triplet
|
||||||
|
a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
|
||||||
|
b = (chunk & 258048) >> 12 // 258048 = (2^6 - 1) << 12
|
||||||
|
c = (chunk & 4032) >> 6 // 4032 = (2^6 - 1) << 6
|
||||||
|
d = chunk & 63 // 63 = 2^6 - 1
|
||||||
|
|
||||||
|
// Convert the raw binary segments to the appropriate ASCII encoding
|
||||||
|
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deal with the remaining bytes and padding
|
||||||
|
if (byteRemainder === 1) {
|
||||||
|
chunk = bytes[mainLength]
|
||||||
|
|
||||||
|
a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2
|
||||||
|
|
||||||
|
// Set the 4 least significant bits to zero
|
||||||
|
b = (chunk & 3) << 4 // 3 = 2^2 - 1
|
||||||
|
|
||||||
|
base64 += encodings[a] + encodings[b] + '=='
|
||||||
|
} else if (byteRemainder === 2) {
|
||||||
|
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]
|
||||||
|
|
||||||
|
a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
|
||||||
|
b = (chunk & 1008) >> 4 // 1008 = (2^6 - 1) << 4
|
||||||
|
|
||||||
|
// Set the 2 least significant bits to zero
|
||||||
|
c = (chunk & 15) << 2 // 15 = 2^4 - 1
|
||||||
|
|
||||||
|
base64 += encodings[a] + encodings[b] + encodings[c] + '='
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64
|
||||||
|
}
|
||||||
|
|
||||||
|
const createCredentials = (publicKey) => {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
prepareOptions(publicKey);
|
||||||
|
|
||||||
|
navigator.credentials.create({publicKey: publicKey})
|
||||||
|
.then(function (newCredentialInfo) {
|
||||||
|
// Send new credential info to server for verification and registration.
|
||||||
|
var cred = {
|
||||||
|
id: newCredentialInfo.id,
|
||||||
|
rawId: base64ArrayBuffer(newCredentialInfo.rawId),
|
||||||
|
response: {
|
||||||
|
clientDataJSON: base64ArrayBuffer(newCredentialInfo.response.clientDataJSON),
|
||||||
|
attestationObject: base64ArrayBuffer(newCredentialInfo.response.attestationObject)
|
||||||
|
},
|
||||||
|
type: newCredentialInfo.type
|
||||||
|
};
|
||||||
|
|
||||||
|
document.getElementById('webauthn').credential.value = JSON.stringify(cred);
|
||||||
|
document.getElementById('webauthn').submit();
|
||||||
|
}).catch(function (err) {
|
||||||
|
console.log('--2--');
|
||||||
|
console.log('WebAuthn create: ' + err);
|
||||||
|
// No acceptable authenticator or user refused consent. Handle appropriately.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user