= 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 ] ); } } ?>