Check if the code is correct. This will accept codes starting from ($discrepancy * $period) sec ago to ($discrepancy * period) sec from now
public verifyCode ( $secret, $code, $discrepancy = 1, $time = null ) |
/** * Enable the Two-factor Authentication. * * @return \Cake\Network\Response|void */ public function enable() { if (!$this->request->is('post')) { return $this->redirect(['action' => 'configure']); } $this->loadModel('UsersTwoFactorAuth'); $userTfa = $this->UsersTwoFactorAuth->find()->where(['UsersTwoFactorAuth.user_id' => $this->Auth->user('id')])->first(); if (is_null($userTfa) || empty($userTfa->secret) || !isset($this->request->data['code'])) { $this->Flash->error(__('Two-factor secret verification failed. Please verify your secret and try again.')); return $this->redirect(['action' => 'configure']); } $tfa = new TwoFactorAuth('Xeta'); if ($tfa->verifyCode($userTfa->secret, $this->request->data['code']) === true && $this->request->data['code'] != $userTfa->current_code) { $this->loadModel('Users'); $user = $this->Users->find()->where(['Users.id' => $this->Auth->user('id')])->select(['id', 'username', 'two_factor_auth_enabled'])->first(); $user->two_factor_auth_enabled = true; $this->Users->save($user); $data = ['session' => $this->request->clientIp() . $this->request->header('User-Agent') . gethostbyaddr($this->request->clientIp()), 'current_code' => $this->request->data['code'], 'recovery_code' => $this->_generateNewRecoveryCode($userTfa->username)]; $this->UsersTwoFactorAuth->patchEntity($userTfa, $data); $this->UsersTwoFactorAuth->save($userTfa); //Logs Event. $this->eventManager()->attach(new Logs()); $event = new Event('Log.User', $this, ['user_id' => $user->id, 'username' => $user->username, 'user_ip' => $this->request->clientIp(), 'user_agent' => $this->request->header('User-Agent'), 'action' => '2FA.enabled']); $this->eventManager()->dispatch($event); $this->Flash->success(__('Two-factor authentication successfully enabled !')); $this->set(compact('user', 'userTfa')); } else { $this->Flash->error(__('Two-factor secret verification failed. Please verify your secret and try again.')); return $this->redirect(['action' => 'configure']); } }
public function testVerifyCodeWorksCorrectly() { $tfa = new TwoFactorAuth('Test', 6, 30); $this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847190)); $this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 29)); //Test discrepancy $this->assertEquals(false, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 30)); //Test discrepancy $this->assertEquals(false, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 - 1)); //Test discrepancy $this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 0)); //Test discrepancy $this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 35)); //Test discrepancy $this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 35)); //Test discrepancy $this->assertEquals(false, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 65)); //Test discrepancy $this->assertEquals(false, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 65)); //Test discrepancy $this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 + 65)); //Test discrepancy $this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 - 65)); //Test discrepancy }
<!doctype html> <html> <head> <title>Demo</title> </head> <body> <ol> <?php require_once 'loader.php'; Loader::register('../lib', 'RobThree\\Auth'); use RobThree\Auth\TwoFactorAuth; $tfa = new TwoFactorAuth('MyApp'); echo '<li>First create a secret and associate it with a user'; $secret = $tfa->createSecret(); echo '<li>Next create a QR code and let the user scan it:<br><img src="' . $tfa->getQRCodeImageAsDataUri('My label', $secret) . '"><br>...or display the secret to the user for manual entry: ' . chunk_split($secret, 4, ' '); $code = $tfa->getCode($secret); echo '<li>Next, have the user verify the code; at this time the code displayed by a 2FA-app would be: <span style="color:#00c">' . $code . '</span> (but that changes periodically)'; echo '<li>When the code checks out, 2FA can be / is enabled; store (encrypted?) secret with user and have the user verify a code each time a new session is started.'; echo '<li>When aforementioned code (' . $code . ') was entered, the result would be: ' . ($tfa->verifyCode($secret, $code) === true ? '<span style="color:#0c0">OK</span>' : '<span style="color:#c00">FAIL</span>'); ?> </ol> <p>Note: Make sure your server-time is <a href="http://en.wikipedia.org/wiki/Network_Time_Protocol">NTP-synced</a>! Depending on the $discrepancy allowed your time cannot drift too much from the users' time!</p> </body> </html>
<a href="/user/tfa/?s=2" class="btn btn-primary"><?php echo $general_language['next']; ?> </a> </div> </div> </body> </html> <?php } else { // Validate code to see if it matches the secret if (Input::exists()) { if (Token::check(Input::get('token'))) { if (isset($_POST['tfa_code'])) { if ($tfa->verifyCode($user->data()->tfa_secret, $_POST['tfa_code']) === true) { $queries->update('users', $user->data()->id, array('tfa_complete' => 1)); Session::flash('usercp_settings', '<div class="alert alert-success">' . $user_language['tfa_successful'] . '</div>'); Redirect::to('/user/settings'); die; } else { Session::flash('tfa', '<div class="alert alert-danger">' . $user_language['invalid_tfa'] . '</div>'); } } else { Session::flash('tfa', '<div class="alert alert-danger">' . $user_language['invalid_tfa'] . '</div>'); } } else { Session::flash('tfa', '<div class="alert alert-danger">' . $user_language['invalid_tfa'] . '</div>'); } } ?>
$to = $user_query[0]->email; $subject = $sitename . ' - ' . $user_language['two_factor_authentication']; $message = $email_language['greeting'] . PHP_EOL . $user_language['tfa_email_contents'] . PHP_EOL . PHP_EOL . $code . PHP_EOL . PHP_EOL . $email_language['thanks'] . PHP_EOL . $sitename; $headers = 'From: ' . $siteemail . "\r\n" . 'Reply-To: ' . $siteemail . "\r\n" . 'X-Mailer: PHP/' . phpversion(); mail($to, $subject, $message, $headers); } } // Ask user to input code now require 'core/includes/tfa_signin.php'; die; } else { // Verify code if ($user_query[0]->tfa_type == 1) { // App $tfa = new TwoFactorAuth('NamelessMC'); if ($tfa->verifyCode($user_query[0]->tfa_secret, $_POST['tfa_code']) !== true) { Session::flash('tfa_signin', '<div class="alert alert-danger">' . $user_language['invalid_tfa'] . '</div>'); require 'core/includes/tfa_signin.php'; die; } } else { // Email // Get the code $code = $user_query[0]->tfa_secret; $code = explode(':', $code); // Check it hasn't expired if ($code[1] < strtotime("-10 minutes")) { // Expired Session::flash('signin', '<div class="alert alert-danger">' . $user_language['invalid_tfa'] . '</div>'); Redirect::to('/signin'); die;
/** * verifyCode * Verifying tfa code with shared secret * @param string $secret of the user * @param string $code from verification form * @return bool */ public function verifyCode($secret, $code) { return $this->tfa->verifyCode($secret, $code); }
/** * Ask to the user the 2FA code and verify it. * * @return \Cake\Network\Response|void */ public function tfa() { if ($this->Auth->user()) { return $this->redirect($this->Auth->redirectUrl()); } if ($this->request->is('post')) { $this->loadModel('UsersTwoFactorAuth'); $id = $this->Cookie->read('CookieTfa'); if (empty($id) || $id == false) { $this->Cookie->delete('CookieTfa'); return $this->redirect($this->Auth->config('loginAction')); } try { $id = Security::decrypt(base64_decode($id), Configure::read('Security.key')); } catch (\Exception $e) { $this->Flash->error(__('The link used for the Two-factor Authentication is incorrect.')); return $this->redirect($this->Auth->config('loginAction')); } $userTfa = $this->UsersTwoFactorAuth->find()->where(['user_id' => $id])->first(); $tfa = new TwoFactorAuth('Xeta'); $isAuthorized = false; $recoveryCodeUsed = false; if ($tfa->verifyCode($userTfa->secret, $this->request->data['code']) === true && $this->request->data['code'] !== $userTfa->current_code) { $isAuthorized = true; //Check recovery code and verify if the recovery code is not already used. } elseif ($userTfa->recovery_code === $this->request->data['code'] && $userTfa->recovery_code_used == false && $this->request->data['code'] !== $userTfa->current_code) { $isAuthorized = true; $recoveryCodeUsed = true; } if ($isAuthorized === true) { $data = ['session' => $this->request->clientIp() . $this->request->header('User-Agent') . gethostbyaddr($this->request->clientIp()), 'current_code' => $recoveryCodeUsed === true ? 'recovery' : $this->request->data['code'], 'recovery_code_used' => $recoveryCodeUsed === true ? 1 : $userTfa->recovery_code_used]; $this->UsersTwoFactorAuth->patchEntity($userTfa, $data); $this->UsersTwoFactorAuth->save($userTfa); //Login the user. $userLogin = $this->Users->find()->where(['id' => $id])->hydrate(false)->first(); unset($userLogin['password']); $this->_handleLogin($userLogin); $this->Cookie->delete('CookieTfa'); //Logs Event. $this->eventManager()->attach(new Logs()); $event = new Event('Log.User', $this, ['user_id' => $userLogin['id'], 'username' => $userLogin['username'], 'user_ip' => $this->request->clientIp(), 'user_agent' => $this->request->header('User-Agent'), 'action' => '2FA.recovery_code.used']); $this->eventManager()->dispatch($event); return $this->redirect(['controller' => 'pages', 'action' => 'home']); } else { $this->Flash->error(__('Two-factor secret verification failed. Please verify your code and try again.')); } } $id = $this->Cookie->read('CookieTfa'); if (empty($id) || $id == false) { $this->Cookie->delete('CookieTfa'); return $this->redirect($this->Auth->config('loginAction')); } }