geforkt von D3Public/oxtotp
add webauthn assertion
Dieser Commit ist enthalten in:
Ursprung
911ff99c83
Commit
b725333e86
@ -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(),
|
||||
|
@ -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 "<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
Normale Datei
18
src/Application/views/blocks/page/info/d3webauthn.tpl
Normale Datei
@ -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}]
|
@ -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--
|
||||
|
@ -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
|
||||
|
23
src/Modules/Application/Controller/d3_totp_ContactController.php
Normale Datei
23
src/Modules/Application/Controller/d3_totp_ContactController.php
Normale Datei
@ -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'));
|
||||
}
|
||||
}
|
@ -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',
|
||||
],
|
||||
]
|
||||
];
|
||||
|
@ -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.
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren