File: /home/wbwebdes/domains/support.wb-webdesign.com/private_html/inc/mfa_functions.inc.php
<?php
/**
*
* This file is part of HESK - PHP Help Desk Software.
*
* (c) Copyright Klemen Stirn. All rights reserved.
* https://www.hesk.com
*
* For the full copyright and license agreement information visit
* https://www.hesk.com/eula.php
*
*/
/* Check if this is a valid include */
use RobThree\Auth\TwoFactorAuth;
if (!defined('IN_SCRIPT')) {die('Invalid attempt');}
// region Email Authentication
function generate_mfa_code() {
return str_pad(random_int(100000, 999999), 6, '0', STR_PAD_LEFT);
}
function hash_and_store_mfa_verification_code($user_id, $verification_code, $user_type = 'STAFF') {
global $hesk_settings;
$hashed_verification_code = hesk_password_hash($verification_code);
// Allow 20 minutes to verify ("INTERVAL 20 MINUTE" is **very** intuitive...)
hesk_dbQuery("INSERT INTO `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfa_verification_tokens` (`user_id`, `verification_token`, `expires_at`, `user_type`)
VALUES (" . intval($user_id) . ",
'" . hesk_dbEscape($hashed_verification_code) . "',
NOW() + INTERVAL 20 MINUTE,
'".hesk_dbEscape($user_type)."')");
}
function send_mfa_email($name, $email, $verification_code) {
global $hesk_settings, $hesklang;
// Demo mode
if (defined('HESK_DEMO')) {
return '';
}
// Prepare and send email
if (!function_exists('hesk_mail')) {
require(HESK_PATH . 'inc/email_functions.inc.php');
}
// Get the email message
list($msg, $html_msg) = hesk_getEmailMessage('mfa_verification',array(),1,0,1);
// Replace message special tags
$name = hesk_msgToPlain($name, 1, 1);
list($msg, $html_msg) = hesk_replace_email_tag('%%NAME%%', $name, $msg, $html_msg);
list($msg, $html_msg) = hesk_replace_email_tag('%%FIRST_NAME%%', hesk_full_name_to_first_name($name), $msg, $html_msg);
list($msg, $html_msg) = hesk_replace_email_tag('%%SITE_TITLE%%', hesk_msgToPlain($hesk_settings['site_title'], 1, 0), $msg, $html_msg);
list($msg, $html_msg) = hesk_replace_email_tag('%%SITE_URL%%', $hesk_settings['site_url'] . ' ', $msg, $html_msg);
list($msg, $html_msg) = hesk_replace_email_tag('%%VERIFICATION_CODE%%', $verification_code, $msg, $html_msg);
// Send email
return hesk_mail($email, [], str_replace('%%VERIFICATION_CODE%%', $verification_code, $hesklang['mfa_verification']), $msg, $html_msg);
}
// Note: Deletes all verification codes upon successful validation
function is_mfa_email_code_valid($user_id, $verification_code, $user_type = 'STAFF') {
global $hesk_settings;
//-- Purge any verification codes that are expired
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfa_verification_tokens`
WHERE `expires_at` < NOW()");
//-- Also purge all but the last 3 codes for a given user
$existing_tokens_rs = hesk_dbQuery("SELECT `id` FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."mfa_verification_tokens` WHERE `user_id` = ".intval($user_id)." ORDER BY `id` DESC");
$index = 0;
while ($row = hesk_dbFetchAssoc($existing_tokens_rs)) {
if ($index++ < 3) {
continue;
}
hesk_dbQuery("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."mfa_verification_tokens`
WHERE `id` = ".intval($row['id'])."
AND `user_type` = '".hesk_dbEscape($user_type)."'");
}
//-- Grab remaining verification codes and see if one matches
$res = hesk_dbQuery("SELECT `verification_token` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfa_verification_tokens`
WHERE `user_id` = " . intval($user_id) . "
AND `user_type` = '" . hesk_dbEscape($user_type) . "'");
while ($row = hesk_dbFetchAssoc($res)) {
if (hesk_password_verify($verification_code, $row['verification_token'])) {
delete_mfa_codes($user_id, $user_type);
return true;
}
}
return false;
}
function delete_mfa_codes($user_id, $user_type = 'STAFF') {
global $hesk_settings;
hesk_dbQuery("DELETE FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . "mfa_verification_tokens`
WHERE `user_id` = " . intval($user_id) . "
AND `user_type` = '".hesk_dbEscape($user_type)."'");
}
// endregion
// region App-based Authentication
function build_tfa_instance() {
global $hesk_settings;
return new TwoFactorAuth($hesk_settings['hesk_title']);
}
function is_mfa_app_code_valid($user_id, $verification_code, $secret = null, $user_type = 'STAFF') {
global $hesk_settings, $hesklang;
$tfa = build_tfa_instance();
try {
$tfa->ensureCorrectTime(array(new \RobThree\Auth\Providers\Time\HttpTimeProvider()));
} catch (\RobThree\Auth\TwoFactorAuthException $e) {
hesk_error(sprintf($hesklang['mfa_server_time_issue'], $e->getMessage()));
}
if ($secret === null) {
$table = $user_type === 'STAFF' ? 'users' : 'customers';
$res = hesk_dbQuery("SELECT `mfa_secret` FROM `" . hesk_dbEscape($hesk_settings['db_pfix']) . $table."` WHERE `id` = " . intval($user_id));
$row = hesk_dbFetchAssoc($res);
$secret = $row['mfa_secret'];
}
return $tfa->verifyCode($secret, $verification_code);
}
// endregion
// region Backup Codes
function generate_and_store_mfa_backup_codes($user_id, $delete_old_codes = true, $user_type = 'STAFF') {
global $hesk_settings;
if ($delete_old_codes) {
delete_mfa_backup_codes($user_id, $user_type);
}
$codes = array();
for ($code_index = 0; $code_index < 10; $code_index++) {
$unique_code_generated = false;
do {
$code = generate_backup_code();
if (!in_array($code, $codes)) {
$codes[] = $code;
$unique_code_generated = true;
store_backup_code($user_id, $code, $user_type);
}
} while (!$unique_code_generated);
}
return $codes;
}
function generate_backup_code() {
$valid_chars = '0123456789abcdef';
$code = '';
for ($char_index = 0; $char_index < 8; $char_index++) {
$code .= $valid_chars[random_int(0, strlen($valid_chars) - 1)];
}
return $code;
}
function store_backup_code($user_id, $code, $user_type = 'STAFF') {
global $hesk_settings;
$hashed_code = hesk_password_hash($code);
hesk_dbQuery("INSERT INTO `".hesk_dbEscape($hesk_settings['db_pfix'])."mfa_backup_codes` (`user_id`, `code`, `user_type`)
VALUES (".intval($user_id).", '".hesk_dbEscape($hashed_code)."', '".hesk_dbEscape($user_type)."')");
}
function delete_mfa_backup_codes($user_id, $user_type = 'STAFF') {
global $hesk_settings;
hesk_dbQuery("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."mfa_backup_codes`
WHERE `user_id` = ".intval($user_id).
" AND `user_type` = '".hesk_dbEscape($user_type)."'");
}
function delete_mfa_backup_code($user_id, $hashed_code, $user_type = 'STAFF') {
global $hesk_settings;
hesk_dbQuery("DELETE FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."mfa_backup_codes`
WHERE `user_id` = ".intval($user_id)." AND `code` = '".hesk_dbEscape($hashed_code)."' AND `user_type` = '".hesk_dbEscape($user_type)."'");
}
function verify_mfa_backup_code($user_id, $code, $user_type = 'STAFF') {
global $hesk_settings;
// Allow spaces, dashes, etc... in the backup code for easier printout
$code = preg_replace('/[^0-9a-f]/', '', strtolower($code));
$res = hesk_dbQuery("SELECT `code` FROM `".hesk_dbEscape($hesk_settings['db_pfix'])."mfa_backup_codes`
WHERE `user_id` = ".intval($user_id)."
AND `user_type` = '".hesk_dbEscape($user_type)."'");
while ($row = hesk_dbFetchAssoc($res)) {
if (hesk_password_verify($code, $row['code'])) {
delete_mfa_backup_code($user_id, $row['code'], $user_type);
return true;
}
}
return false;
}
// endregion