diff --git a/CHANGELOG.md b/CHANGELOG.md index 512475a..89b5630 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# v3.6.2 +## 01/12/2022 + +1. [](#new) + * Support for `YubiKey OTP` 2-Factor authenticator + * Requires Grav `v1.7.27` + # v3.6.1 ## 01/03/2022 diff --git a/blueprints.yaml b/blueprints.yaml index 174860a..c771ca5 100644 --- a/blueprints.yaml +++ b/blueprints.yaml @@ -1,7 +1,7 @@ name: Login slug: login type: plugin -version: 3.6.1 +version: 3.6.2 testing: false description: Enables user authentication and login screen. icon: sign-in @@ -15,7 +15,7 @@ bugs: https://github.com/getgrav/grav-plugin-login/issues license: MIT dependencies: - - { name: grav, version: '>=1.7.21' } + - { name: grav, version: '>=1.7.27' } - { name: form, version: '>=5.1.0' } - { name: email, version: '>=3.1.0' } diff --git a/classes/TwoFactorAuth/TwoFactorAuth.php b/classes/TwoFactorAuth/TwoFactorAuth.php index 091480e..ee29292 100644 --- a/classes/TwoFactorAuth/TwoFactorAuth.php +++ b/classes/TwoFactorAuth/TwoFactorAuth.php @@ -10,8 +10,14 @@ namespace Grav\Plugin\Login\TwoFactorAuth; use Grav\Common\Grav; +use Grav\Common\HTTP\Client; +use Grav\Common\Utils; use RobThree\Auth\TwoFactorAuth as Auth; use RobThree\Auth\TwoFactorAuthException; +use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; /** * Class TwoFactorAuth @@ -55,6 +61,10 @@ public function createSecret($bits = 160) */ public function verifyCode($secret, $code) { + if (!$secret || !$code) { + return false; + } + $secret = str_replace(' ', '', $secret); return $this->twoFa->verifyCode($secret, $code); @@ -73,4 +83,36 @@ public function getQrImageData($username, $secret) return $this->twoFa->getQRCodeImageAsDataUri($label, $secret); } + + /** + * @param string $yubikey_id + * @param string $otp + * @return bool + */ + public function verifyYubikeyOTP(string $yubikey_id, string $otp): bool + { + // Quick sanity check + if (!$yubikey_id || !$otp || !Utils::startsWith($otp, $yubikey_id)) { + return false; + } + + $api_url = "https://api.yubico.com/wsapi/2.0/verify?id=1&otp=%s&nonce=%s"; + $client = Client::getClient(); + + $url = sprintf($api_url, $otp, Utils::getNonce('yubikey')); + + try { + $response = $client->request('GET', $url); + if ($response->getStatusCode() === 200) { + $content = $response->getContent(); + if (Utils::contains($content, 'status=OK')) { + return true; + } + } + } catch (TransportExceptionInterface|ClientExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface $e) { + return false; + } + + return false; + } }