|
<?php |
|
class OTP { |
|
// (A) CONSTRUCTOR - CONNECT TO DATABASE |
|
private $pdo = null; |
|
private $stmt = null; |
|
public $error = ""; |
|
function __construct() { |
|
$this->pdo = new PDO( |
|
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET, |
|
DB_USER, DB_PASSWORD, [ |
|
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
|
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC |
|
]); |
|
} |
|
|
|
// (B) DESTRUCTOR - CLOSE CONNECTION |
|
function __destruct() { |
|
if ($this->stmt !== null) { $this->stmt = null; } |
|
if ($this->pdo !== null) { $this->pdo = null; } |
|
} |
|
|
|
// (C) HELPER - RUN SQL QUERY |
|
function query ($sql, $data=null) : void { |
|
$this->stmt = $this->pdo->prepare($sql); |
|
$this->stmt->execute($data); |
|
} |
|
|
|
// (D) GENERATE OTP |
|
function generate ($email) { |
|
// (D1) CHECK EXISTING OTP REQUEST |
|
$this->query("SELECT * FROM `otp` WHERE `email`=?", [$email]); |
|
$otp = $this->stmt->fetch(); |
|
if (is_array($otp) && (strtotime("now") < strtotime($otp["timestamp"]) + (OTP_VALID * 60))) { |
|
$this->error = "You already have a pending OTP."; |
|
return false; |
|
} |
|
|
|
// (D2) CREATE RANDOM OTP |
|
$alphabets = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; |
|
$count = strlen($alphabets) - 1; |
|
$pass = ""; |
|
for ($i=0; $i<OTP_LEN; $i++) { $pass .= $alphabets[rand(0, $count)]; } |
|
$this->query( |
|
"REPLACE INTO `otp` (`email`, `pass`) VALUES (?,?)", |
|
[$email, password_hash($pass, PASSWORD_DEFAULT)] |
|
); |
|
|
|
// (D3) SEND VIA EMAIL |
|
if (@mail($email, "Your OTP", |
|
"Your OTP is $pass. Enter at <a href='http://localhost/3b-challenge.php'>SITE</a>.", |
|
implode("\r\n", ["MIME-Version: 1.0", "Content-type: text/html; charset=utf-8"]) |
|
)) { return true; } |
|
else { |
|
$this->error = "Failed to send OTP email."; |
|
return false; |
|
} |
|
} |
|
|
|
// (E) CHALLENGE OTP |
|
function challenge ($email, $pass) { |
|
// (E1) GET THE OTP ENTRY |
|
$this->query("SELECT * FROM `otp` WHERE `email`=?", [$email]); |
|
$otp = $this->stmt->fetch(); |
|
|
|
// (E2) CHECK - NOT FOUND |
|
if (!is_array($otp)) { |
|
$this->error = "The specified OTP request is not found."; |
|
return false; |
|
} |
|
|
|
// (E3) CHECK - EXPIRED |
|
if (strtotime("now") > strtotime($otp["timestamp"]) + (OTP_VALID * 60)) { |
|
$this->error = "OTP has expired."; |
|
return false; |
|
} |
|
|
|
// (E4) CHECK - INCORRECT PASSWORD |
|
if (!password_verify($pass, $otp["pass"])) { |
|
$this->error = "Incorrect OTP."; |
|
return false; |
|
} |
|
|
|
// (E5) OK - DELETE OTP REQUEST |
|
$this->query("DELETE FROM `otp` WHERE `email`=?", [$email]); |
|
return true; |
|
} |
|
} |
|
|
|
// (F) DATABASE SETTINGS - CHANGE TO YOUR OWN! |
|
define("DB_HOST", "localhost"); |
|
define("DB_NAME", "test"); |
|
define("DB_CHARSET", "utf8mb4"); |
|
define("DB_USER", "root"); |
|
define("DB_PASSWORD", ""); |
|
|
|
// (G) OTP SETTINGS |
|
define("OTP_VALID", "15"); // otp valid for n minutes |
|
define("OTP_LEN", "8"); // otp length |
|
|
|
// (H) NEW OTP OBJECT |
|
$_OTP = new OTP(); |