Files
shopPRO/autoload/admin/factory/class.Users.php
Jacek Pyziak 290aa31aa7 Add two-factor authentication fields to pp_users table and update .htaccess for security
- Added columns for two-factor authentication (2FA) in the pp_users table:
  - twofa_enabled (TINYINT)
  - twofa_email (VARCHAR)
  - twofa_code_hash (VARCHAR)
  - twofa_expires_at (DATETIME)
  - twofa_sent_at (DATETIME)
  - twofa_failed_attempts (INT)
- Updated the twofa_enabled and twofa_email for user with id 0.
- Enhanced .htaccess to disable directory listing, block execution of sensitive files, and prevent serving hidden files.
2025-12-11 23:57:22 +01:00

215 lines
6.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace admin\factory;
class Users
{
static public function verify_twofa_code(int $userId, string $code): bool
{
$user = self::get_by_id( $userId );
if (!$user) return false;
if ((int)$user['twofa_failed_attempts'] >= 5)
{
return false; // zbyt wiele prób
}
// sprawdź ważność
if (empty($user['twofa_expires_at']) || time() > strtotime($user['twofa_expires_at']))
{
// wyczyść po wygaśnięciu
self::update_by_id($userId, [
'twofa_code_hash' => null,
'twofa_expires_at' => null,
]);
return false;
}
$ok = (!empty($user['twofa_code_hash']) && password_verify($code, $user['twofa_code_hash']));
if ($ok)
{
// sukces: czyścimy wszystko
self::update_by_id($userId, [
'twofa_code_hash' => null,
'twofa_expires_at' => null,
'twofa_sent_at' => null,
'twofa_failed_attempts' => 0,
'last_logged' => date('Y-m-d H:i:s'),
]);
return true;
}
// zła próba — inkrementacja
self::update_by_id($userId, [
'twofa_failed_attempts' => (int)$user['twofa_failed_attempts'] + 1,
'last_error_logged' => date('Y-m-d H:i:s'),
]);
return false;
}
static public function get_by_id(int $userId): ?array {
global $mdb;
return $mdb->get('pp_users', '*', ['id' => $userId]) ?: null;
}
static public function update_by_id(int $userId, array $data): bool {
global $mdb;
return (bool)$mdb->update('pp_users', $data, ['id' => $userId]);
}
static public function send_twofa_code(int $userId, bool $resend = false): bool {
$user = self::get_by_id($userId);
if ( !$user )
return false;
if ( (int)$user['twofa_enabled'] !== 1 )
return false;
$to = $user['twofa_email'] ?: $user['login'];
if (!filter_var($to, FILTER_VALIDATE_EMAIL))
return false;
if ( $resend && !empty( $user['twofa_sent_at'] ) ) {
$last = strtotime($user['twofa_sent_at']);
if ($last && (time() - $last) < 30)
return false;
}
$code = random_int(100000, 999999);
$hash = password_hash((string)$code, PASSWORD_DEFAULT);
self::update_by_id( $userId, [
'twofa_code_hash' => $hash,
'twofa_expires_at' => date('Y-m-d H:i:s', time() + 10 * 60), // 10 minut
'twofa_sent_at' => date('Y-m-d H:i:s'),
'twofa_failed_attempts' => 0,
] );
$subject = 'Twój kod logowania 2FA';
$body = "Twój kod logowania do panelu administratora: {$code}. Kod jest ważny przez 10 minut. Jeśli to nie Ty inicjowałeś logowanie zignoruj tę wiadomość i poinformuj administratora.";
$sent = \S::send_email($to, $subject, $body);
if (!$sent) {
$headers = "MIME-Version: 1.0\r\n";
$headers .= "Content-type: text/plain; charset=UTF-8\r\n";
$headers .= "From: no-reply@" . ($_SERVER['HTTP_HOST'] ?? 'localhost') . "\r\n";
$encodedSubject = mb_encode_mimeheader($subject, 'UTF-8');
$sent = mail($to, $encodedSubject, $body, $headers);
}
return $sent;
}
public static function user_delete( $user_id )
{
global $mdb;
return $mdb -> delete( 'pp_users', [ 'id' => (int)$user_id ] );
}
public static function user_details( $user_id )
{
global $mdb;
return $mdb -> get( 'pp_users', '*', [ 'id' => (int)$user_id ] );
}
public static function user_save( $user_id = '', $login, $status, $password, $password_re, $admin, $twofa_enabled = 0, $twofa_email = '' )
{
global $mdb, $lang, $config;
if ( !$user_id )
{
if ( strlen( $password ) < 5 )
return $response = [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
if ( $password != $password_re )
return $response = [ 'status' => 'error', 'msg' => 'Podane hasła są różne' ];
if ( $mdb -> insert( 'pp_users', [
'login' => $login,
'status' => $status == 'on' ? 1 : 0,
'admin' => $admin,
'password' => md5( $password ),
'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0,
'twofa_email' => $twofa_email
] ) )
{
return $response = [ 'status' => 'ok', 'msg' => 'Użytkownik został zapisany.' ];
}
}
else
{
if ( $password and strlen( $password ) < 5 )
return $response = [ 'status' => 'error', 'msg' => 'Podane hasło jest zbyt krótkie.' ];
if ( $password and $password != $password_re )
return $response = [ 'status' => 'error', 'msg' => 'Podane hasła są różne' ];
if ( $password )
$mdb -> update( 'pp_users', [
'password' => md5( $password )
], [
'id' => (int)$user_id
] );
$mdb -> update( 'pp_users', [
'login' => $login,
'admin' => $admin,
'status' => $status == 'on' ? 1 : 0,
'twofa_enabled' => $twofa_enabled == 'on' ? 1 : 0,
'twofa_email' => $twofa_email
], [
'id' => (int)$user_id
] );
return $response = [ 'status' => 'ok', 'msg' => 'Uzytkownik został zapisany.' ];
}
}
public static function check_login( $login, $user_id )
{
global $mdb;
if ( $mdb -> get( 'pp_users', 'login', [ 'AND' => [ 'login' => $login, 'id[!]' => (int)$user_id ] ] ) )
return $response = [ 'status' => 'error', 'msg' => 'Podany login jest już zajęty.' ];
return $response = [ 'status' => 'ok' ];
}
public static function logon( $login, $password )
{
global $mdb;
if ( !$mdb -> get( 'pp_users', '*', [ 'login' => $login ] ) )
return 0;
if ( !$mdb -> get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'error_logged_count[<]' => 5 ] ] ) )
return -1;
if ( $mdb -> get( 'pp_users', '*', [ 'AND' => [ 'login' => $login, 'status' => 1, 'password' => md5( $password ) ] ] ) )
{
$mdb -> update( 'pp_users', [ 'last_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count' => 0 ], [ 'login' => $login ] );
return 1;
}
else
{
$mdb -> update( 'pp_users', [ 'last_error_logged' => date( 'Y-m-d H:i:s' ), 'error_logged_count[+]' => 1 ], [ 'login' => $login ] );
if ( $mdb -> get( 'pp_users', 'error_logged_count', [ 'login' => $login ] ) >= 5 )
{
$mdb -> update( 'pp_users', [ 'status' => 0 ], [ 'login' => $login ] );
return -1;
}
}
return 0;
}
public static function details( $login )
{
global $mdb;
return $mdb -> get( 'pp_users', '*', [ 'login' => $login ] );
}
}
?>