diff --git a/app/Libraries/Aauth/CAPTCHA.php b/app/Libraries/Aauth/CAPTCHA.php index 831e796..8175c87 100644 --- a/app/Libraries/Aauth/CAPTCHA.php +++ b/app/Libraries/Aauth/CAPTCHA.php @@ -28,7 +28,6 @@ use \App\Models\Aauth\LoginAttemptModel; */ class CAPTCHA extends \App\Libraries\Aauth { - /** * Verify Response * @@ -78,17 +77,15 @@ class CAPTCHA extends \App\Libraries\Aauth $answer = json_decode($request, true); - if (trim($answer['success']) === true) - { - return ['success' => true]; - } - else + if (trim($answer['success']) !== true) { return [ 'success' => false, 'errorCodes' => $answer['error-codes'], ]; } + + return ['success' => true]; } /** @@ -110,8 +107,8 @@ class CAPTCHA extends \App\Libraries\Aauth if ($this->config->captchaType === 'recaptcha') { - $content .= "
"; - $content = ''; + $content = "
"; + $content .= ''; } else if ($this->config->captchaType === 'hcaptcha') { diff --git a/app/Libraries/Aauth/TOTP.php b/app/Libraries/Aauth/TOTP.php index ffee837..0670c12 100644 --- a/app/Libraries/Aauth/TOTP.php +++ b/app/Libraries/Aauth/TOTP.php @@ -57,19 +57,21 @@ class TOTP extends \App\Libraries\Aauth */ public function generateUniqueTotpSecret() { - $stop = false; + $endSecret = false; - while (! $stop) + $userVariableModel = new UserVariableModel(); + + while (! $endSecret) { $secret = OTPHP_TOTP::create(); - if ($userVariable !== $userVariableModel->where(['data_key' => 'totp_secret', 'data_value' => $secret, 'system' => 1])->getFirstRow('array')) + if ($secret->getSecret() !== $userVariableModel->where(['data_key' => 'totp_secret', 'data_value' => $secret->getSecret(), 'system' => 1])->getFirstRow('array')) { - $stop = true; - - return $secret; + $endSecret = $secret->getSecret(); } } + + return $endSecret; } /** @@ -81,9 +83,10 @@ class TOTP extends \App\Libraries\Aauth * * @return string */ - public function generateTotpQrCode(string $secret) + public function generateTotpQrCode(string $secret, string $label = '') { $totp = OTPHP_TOTP::create($secret); + $totp->setLabel($label); return $totp->getQrCodeUri(); } @@ -98,40 +101,29 @@ class TOTP extends \App\Libraries\Aauth */ public function verifyUserTotpCode(int $totpCode, string $userId = null) { - if (! $this->isTotpRequired()) - { - return true; - } - - if (! $userId) - { - $userId = (int) @$this->session->user['id']; - } - - if (empty($totpCode)) + if ($this->isTotpRequired()) { - $this->error(lang('Aauth.requiredTOTPCode')); - - return false; - } + if (! $userId) + { + $userId = (int) @$this->session->user['id']; + } - $userVariableModel = new UserVariableModel(); + $userVariableModel = new UserVariableModel(); - $totpSecret = $userVariableModel->find($userId, 'totp_secret', true); - $totp = OTPHP_TOTP::create($totpSecret); + if ($totpSecret = $userVariableModel->find($userId, 'totp_secret', true)) + { + $totp = OTPHP_TOTP::create($totpSecret); - if (! $totp->verify($totpCode)) - { - $this->error(lang('Aauth.invalidTOTPCode')); + if (! $totp->verify($totpCode)) + { + return false; + } - return false; + unset($_SESSION['user']['totp_required']); + } } - else - { - unset($_SESSION['user']['totp_required']); - return true; - } + return true; } /** diff --git a/tests/Aauth/Libraries/Aauth/CAPTCHATest.php b/tests/Aauth/Libraries/Aauth/CAPTCHATest.php new file mode 100644 index 0000000..217cc2a --- /dev/null +++ b/tests/Aauth/Libraries/Aauth/CAPTCHATest.php @@ -0,0 +1,109 @@ +response = service('response'); + $this->request = new IncomingRequest(new App(), new URI(), null, new UserAgent()); + Services::injectMock('request', $this->request); + + $this->library = new Aauth(null, true); + $_COOKIE = []; + $_SESSION = []; + } + + public function tearDown() + { + } + + protected function getInstance($options = []) + { + $defaults = [ + 'sessionDriver' => 'CodeIgniter\Session\Handlers\FileHandler', + 'sessionCookieName' => 'ci_session', + 'sessionExpiration' => 7200, + 'sessionSavePath' => 'null', + 'sessionMatchIP' => false, + 'sessionTimeToUpdate' => 300, + 'sessionRegenerateDestroy' => false, + 'cookieDomain' => '', + 'cookiePrefix' => '', + 'cookiePath' => '/', + 'cookieSecure' => false, + ]; + + $config = (object)$defaults; + + $session = new MockSession(new FileHandler($config, Services::request()->getIPAddress()), $config); + $session->setLogger(new TestLogger(new Logger())); + $session->start(); + + return $session; + } + + //-------------------------------------------------------------------- + + public function testGenerateCaptchaHtml() + { + $config = new AauthConfig(); + $config->captchaEnabled = true; + $this->library = new Aauth($config, true); + + $this->assertEquals('', $this->library->generateCaptchaHtml()); + + $this->library->login('admina@example.com', 'password123456'); + $this->library->login('admina@example.com', 'password123456'); + $this->library->login('admina@example.com', 'password123456'); + $this->library->login('admina@example.com', 'password123456'); + $this->library->login('admina@example.com', 'password123456'); + $this->library->login('admina@example.com', 'password123456'); + $this->library->login('admina@example.com', 'password123456'); + + $this->assertContains('https://www.google.com/recaptcha', $this->library->generateCaptchaHtml()); + + $config->captchaType = 'hcaptcha'; + $this->library = new Aauth($config, true); + + $this->assertContains('https://hcaptcha.com/1', $this->library->generateCaptchaHtml()); + } + + public function testVerifyResponse() + { + $config = new AauthConfig(); + $config->captchaEnabled = true; + $this->library = new Aauth($config, true); + + $this->assertContains('missing-input', $this->library->verifyResponse(null)['errorCodes']); + $this->assertContains('invalid-input-response', $this->library->verifyResponse('0123456789')['errorCodes']); + + $config->captchaType = 'hcaptcha'; + $this->library = new Aauth($config, true); + $this->assertContains('invalid-input-response', $this->library->verifyResponse('0123456789')['errorCodes']); + } +} diff --git a/tests/Aauth/Libraries/Aauth/TOTPTest.php b/tests/Aauth/Libraries/Aauth/TOTPTest.php new file mode 100644 index 0000000..fce21b8 --- /dev/null +++ b/tests/Aauth/Libraries/Aauth/TOTPTest.php @@ -0,0 +1,183 @@ +library = new Aauth(null, true); + $this->config = new AauthConfig(); + $_COOKIE = []; + $_SESSION = []; + } + + public function tearDown() + { + } + + protected function getInstance($options = []) + { + $defaults = [ + 'sessionDriver' => 'CodeIgniter\Session\Handlers\FileHandler', + 'sessionCookieName' => 'ci_session', + 'sessionExpiration' => 7200, + 'sessionSavePath' => 'null', + 'sessionMatchIP' => false, + 'sessionTimeToUpdate' => 300, + 'sessionRegenerateDestroy' => false, + 'cookieDomain' => '', + 'cookiePrefix' => '', + 'cookiePath' => '/', + 'cookieSecure' => false, + ]; + + $config = (object)$defaults; + + $session = new MockSession(new FileHandler($config, Services::request()->getIPAddress()), $config); + $session->setLogger(new TestLogger(new Logger())); + $session->start(); + + return $session; + } + + //-------------------------------------------------------------------- + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testUpdateUserTotpSecret() + { + $config = new AauthConfig(); + $config->totpEnabled = true; + $this->library = new Aauth($config, true); + + $this->assertTrue($this->library->updateUserTotpSecret(99, 'TESTSECRET99')); + $this->seeInDatabase($this->config->dbTableUserVariables, [ + 'user_id' => 99, + 'data_key' => 'totp_secret', + 'data_value' => 'TESTSECRET99', + 'system' => true, + ]); + + $session = $this->getInstance(); + $this->library = new Aauth($config, $session); + $session->set('user', [ + 'id' => 1, + 'loggedIn' => true, + ]); + + $this->assertTrue($this->library->updateUserTotpSecret(null, 'TESTSECRET1')); + $this->seeInDatabase($this->config->dbTableUserVariables, [ + 'user_id' => 1, + 'data_key' => 'totp_secret', + 'data_value' => 'TESTSECRET1', + 'system' => true, + ]); + } + + public function testGenerateUniqueTotpSecret() + { + $config = new AauthConfig(); + $config->totpEnabled = true; + $this->library = new Aauth($config, true); + + $this->assertInternalType('string', $this->library->generateUniqueTotpSecret()); + } + + public function testGenerateTotpQrCode() + { + $config = new AauthConfig(); + $config->totpEnabled = true; + $this->library = new Aauth($config, true); + + $this->assertInternalType('string', $this->library->generateTotpQrCode('testsecret')); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testVerifyUserTotpCode() + { + $config = new AauthConfig(); + $session = $this->getInstance(); + + $config->totpEnabled = true; + $this->library = new Aauth($config, $session); + + $this->assertTrue($this->library->verifyUserTotpCode('999000', 1)); + + $this->library = new Aauth($config, $session); + $session->set('user', [ + 'id' => 1, + 'loggedIn' => true, + ]); + + $this->assertTrue($this->library->verifyUserTotpCode('999000')); + + $session = $this->getInstance(); + $this->library = new Aauth($config, $session); + $session->set('user', [ + 'id' => 1, + 'loggedIn' => true, + 'totp_required' => true, + ]); + + $this->assertTrue($this->library->verifyUserTotpCode('999000')); + $this->assertTrue($this->library->verifyUserTotpCode('999000', 1)); + + $this->hasInDatabase($this->config->dbTableUserVariables, [ + 'user_id' => 1, + 'data_key' => 'totp_secret', + 'data_value' => 'JBSWY3DPEHPK3PXP', + 'system' => true, + ]); + $this->assertFalse($this->library->verifyUserTotpCode('999000', 1)); + + $totp = TOTP::create('JBSWY3DPEHPK3PXP'); + $totp->setLabel(''); + + $this->assertTrue($this->library->verifyUserTotpCode($totp->now(), 1)); + } + + /** + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function testIsTotpRequired() + { + $config = new AauthConfig(); + $config->totpEnabled = true; + $this->library = new Aauth($config, true); + + $this->assertFalse($this->library->isTotpRequired()); + + $session = $this->getInstance(); + $this->library = new Aauth($config, $session); + $session->set('user', [ + 'id' => 1, + 'loggedIn' => true, + 'totp_required' => true, + ]); + $this->assertTrue($this->library->isTotpRequired()); + } +}