Динамически меняющееся действие HTML-формы?(стратегия регулирования) - PullRequest
0 голосов
/ 26 января 2019

Я просмотрел несколько вопросов и ответов, касающихся неудачных попыток входа в систему и регулирования количества пользователей в зависимости от количества попыток входа с неверным паролем.

Что именно означает «блокирование» попыток пользователя?Как это сделать?Направляя действие формы на другую страницу?

Возможно, я упускаю очевидное, поэтому я попытался придумать способ «блокировать» попытки пользователей, если они пытаются войти в систему более x раз: изменить имя входадействие формы, указывающее на другую страницу, которая вообще не попадает в базу данных, она просто говорит что-то вроде «повторить попытку через 15 минут».

Мой вопрос состоит в том, что подразумевается под «блокированием пользователей» икак / где я могу получить эту информацию?Похоже, что-то, что уже было сделано, так что мне кажется, что я просто пытаюсь заново изобрести колесо своим собственным методом.

Это полный код в PHP, и большая часть кредитов идет накомбинация различных ответов в StackOverflow, а также некоторых моих собственных:

<div class="nav-login">
        <?php
            //check if user is logged in
            if (isset($_SESSION['u_id'])) {
                echo '<form action="includes/logout.inc.php" method="POST">
                    <button type="submit" name="submit">Logout</button>
                </form>';
            }
            //creat a default form action which processes the login form normally
            $form_action = 'includes/login.inc.php';

            //if the above page responded with an error in the url, which looks something like login?pwd=error&uid=admin123
            if (isset($_GET['pwd']) && isset($_GET['uid']) && $_GET['pwd'] === 'error') {
                //create empty variables which can be used later on
                $response = '';
                $captcha = '';
                //get the username which was attempted
                $uid = $_GET['uid'];

                //insert the failed logins into a database along with his IP address 
                //(note: learn about and use prepared statements here!)
                mysqli_query($conn, "INSERT INTO failed_logins SET username = '$uid', ip_address = INET_ATON('".get_client_ip()."'), attempted = CURRENT_TIMESTAMP;") or die(mysqli_error($conn));

                // array of throttling
                $throttle = array(
                    //attempts => delay
                    10 => 7000, //secs
                    20 => 12000, //secs
                    30 => 'recaptcha'); //if it reaches this level, only an email link can get them back into their account, where they can either reset password or login through that link.

                // retrieve the latest failed login attempts
                $sql_1 = 'SELECT MAX(attempted) AS attempted FROM failed_logins';
                $result_1 = mysqli_query($conn, $sql_1) or die(mysqli_error($conn));
                if (mysqli_num_rows($result_1) > 0) {
                    $row_1 = mysqli_fetch_assoc($result_1);
                    //if more than 0 results appear, record the latest attempt timestamp inside a variable
                    $latest_attempt = (int) date('U', strtotime($row_1['attempted']));

                    // get the number of failed attempts
                    $sql_1 = 'SELECT COUNT(1) AS failed FROM failed_logins WHERE attempted > DATE_SUB(NOW(), INTERVAL 15 minute)';
                    $result_1 = mysqli_query($conn, $sql_1) or die(mysqli_error($conn));
                    if (mysqli_num_rows($result_1) > 0) {
                        // get the returned row
                        $row_1 = mysqli_fetch_assoc($result_1);
                        $failed_attempts = (int) $row_1['failed'];

                        // assume the number of failed attempts was stored in $failed_attempts
                        krsort($throttle);
                        foreach ($throttle as $attempts => $delay) {
                            if ($failed_attempts > $attempts) {
                                // we need to throttle based on delay
                                if (is_numeric($delay)) {

                                    $remaining_delay = time() - $latest_attempt - $delay;

                                    $remaining = abs(number_format($remaining_delay)); //abs() converts a negative number to a positive format. This is because I want to use the timer for a javascript countdown.

                                    // output remaining delay
                                    $response .= '<p id="counting_string">You must wait <span id="counter">' . $remaining . '</span> seconds before your next login attempt.</p>'; 
                                    //Okay, the user got the message, but how do I ban him from clicking login until the countdown is over? (aside from disabling the button via javascript...which seems pretty weak/lame)
                                    //option 1, change the form action?... 
                                    $form_action = "includes/failed_logins.inc.php";

                                } else {
                                    //if the user reaches above 30 attempts, display the captcha. Even if he types in the correct password this time, tho, he won't get to log in, until he clicks on the email link we sent him (with a token, kind of like a reset-password-email-token).
                                    //implement a captcha/recaptcha here
                                    $captcha .= 'captcha!';
                                    //keep the fake form_action
                                    $form_action = "includes/failed_logins.inc.php";
                                    // code to display recaptcha on login form goes here
                                }
                                break;
                            }
                        }        
                    }
                }
            }

            mysqli_close($conn); // Always close your SQL connection and free the memory or you may be needlessly holding a SQL connectio , remember that a shared hosting environment only allows max 30 SQL connections at the same time

            echo '<form action="'.$form_action.'" method="POST">'; // login.inc.php or failed_logins.inc.php 
            echo '
                <input type="text" name="uid" placeholder="Username/e-mail">
                <input type="password" name="pwd" placeholder="password">
                <button type="submit" name="submit" id="login_submit">Login</button>
            </form>';
            if (!empty($response)) {
                echo $response;
            }
            if (!empty($captcha)) {
                echo $captcha;
            }
            echo '<a href="signup.php">Sign up</a>';

            ?>
        </div>

Таблица выглядит следующим образом: (я знаю, что ip_address - это тип данных int (11) ... это из другого ответа StackOverflow)- он использует INET_ATON для вставок)

+------------+------------------+------+-----+--+
|   Field    |       Type       | Null | Key |  |
+------------+------------------+------+-----+--+
| id         | int(11) unsigned | NO   | PRI |  |
|            | NULL             |      |     |  |
|            | auto_increment   |      |     |  |
| username   | varchar(16)      | NO   |     |  |
|            | NULL             |      |     |  |
|            |                  |      |     |  |
| ip_address | int(11) unsigned | YES  |     |  |
|            | NULL             |      |     |  |
|            |                  |      |     |  |
| attempted  | datetime         | NO   | MUL |  |
|            | NULL             |      |     |  |
|            |                  |      |     |  |
+------------+------------------+------+-----+--+

Кроме того, для большей ясности это выглядит так: include / login.inc.php выглядит так:

<?php

//We created this last, but place it before anything else in the document. It simply means that users who are logged in should be able to see this page.
session_start();

//Check if user actually clicked submit button, else throw an error 
if (isset($_POST['submit'])) {

include 'dbh.inc.php';

$uid = mysqli_real_escape_string($conn, $_POST['uid']);
$pwd = mysqli_real_escape_string($conn, $_POST['pwd']);

//Error handlers
//Check if inputs are empty
if (empty($uid) || empty($pwd)) {
    header("Location: ../index.php?login=empty");
    exit();
} else {
    //Check if username exists in database
    //Prepare these fields! (Use prepared statements here)
    $sql = "SELECT * FROM users WHERE user_uid='$uid' OR user_email='$uid'"; 
    //check if username or email is existing
    $result = mysqli_query($conn, $sql);
    $resultCheck = mysqli_num_rows($result);
    if ($resultCheck < 1) {
        header("Location: ../index.php?login=error");
        exit();
    } else {
        if ($row = mysqli_fetch_assoc($result)) { //This takes all the data we requested from the user_uid column in the database, and places it into an array called $row. If we wanted, we could echo out the username like this: echo $row['user_uid'];
            //De-hashing the password
            //Again, use prepared statement
            $hashedPwdCheck = password_verify($pwd, $row['user_pwd']); //Check if password matches, returns true or false
            if ($hashedPwdCheck == false) {
                //wrong password!
                header("Location: ../index.php?pwd=error&uid=".$uid);
                exit();
            } elseif ($hashedPwdCheck == true) {
                //Log in the user here
                $_SESSION['u_id'] = $row['user_id'];
                $_SESSION['u_first'] = $row['user_first'];
                $_SESSION['u_last'] = $row['user_last'];
                $_SESSION['u_email'] = $row['user_email'];
                $_SESSION['u_uid'] = $row['user_uid'];
                header("Location: ../index.php?login=success");

                exit();
            }
        }
    }
}
} else {
    header("Location: ../index.php?login=error");
    exit();
}

Я прокомментировал каждый шагчтобы отслеживать, что я делаю ... Код выглядит «странным» или «неправильным» в любом случае?Честно говоря, я хотел бы знать, как правильно «блокировать» попытки пользователей во время этих задержек, пожалуйста?

...