0

I am currently trying to add 'lockout' functionality to my login system. I've done it by adding a 'blocked' column in my 'users' table in the DB, and adding a table called 'Failed_Logins' which just record the username and time of the failed login.

If a user incorrectly logs in 5 times within an hour, the account is locked for 15 minutes. The locked part works fine, but I am having great difficulty unlocking it after 15 minutes. I want to do some sort of delayed SQL query but nothing I have tried is working.

Inside my login function, I have this:

// check if user is locked out. If they aren't, carry on with login checks
        if(isBlocked($conn, $username) == 0){
            failed_logins_action($conn, $username, failed_logins_check($conn, $username));

The functions are then as below:

function failed_logins_check($conn, $username) {
    require("dbConn.php");
    // prepared statement to delete any failed logins older than 1 hour
    deletes_old_failed_logins($conn, $username);
    
    // check for failed logins within the hour
    $query = $connect -> prepare("SELECT * FROM `Failed_Logins` WHERE `Timestamp` >= date_sub(now(),interval 1 hour) AND `Username` = ?;");
    $query -> bind_param("s", $username);
    $query->execute();
    $query -> store_result(); //Transfers a result set from a prepared statement
    // save the number of rows to a variable
    $row_count = $query -> num_rows;
    
    return $row_count;
}

and

function failed_logins_action($conn, $username, $amt) {
    // for testing purposes
    echo $amt;
    require("dbConn.php");
    
    $block_user = $conn -> prepare("UPDATE `users`
        SET Blocked = ? WHERE Username = ?;");
    $block_user -> bind_param("is", $blocked, $username);
    
    
    if ($amt >= 10) {
        // blocked level 2 = perma
        $blocked = 2;
    } elseif ($amt >= 5) {
        // blocked level 1 = temp (15 mins)
        $blocked = 1;
    } else {
        // not blocked
        $blocked = 0;
    }
    $block_user->execute();
    $block_user->close();
    
    if ($blocked == 1) {
    $unblock_user = $conn -> prepare("CREATE EVENT myevent
    ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 1 MINUTE
    DO
    UPDATE `users`
        SET Blocked = ? WHERE Username = ?;");
        $unblock_user -> bind_param("is", 0, $username);
        $unblock_user->execute();
        //$unblock_user->close();
    }
}

It's the final IF statement in the failed_logins_action function which is the problem.

I get the following error when I try to login for the 5th time incorrectly:

Fatal error: Uncaught mysqli_sql_exception: This command is not supported in the prepared statement protocol yet in /home/devpath/public_html/includes/functions.php:382 Stack trace: #0 /home/devpath/public_html/includes/functions.php(382): mysqli->prepare('CREATE EVENT my...') #1

Can somebody please tell me how to delay the unlocking of the account by 15 minutes? In the above code I have it set to 1 minute just for testinf purposes.

I have also tried using PHP's 'sleep' method followed by a regular prepared UPDATE statement, however this did not work. If the user leaves the page during the delay, it just does not activate I think.

I have also tried 'WAITFOR DELAY' but phpmyadmin doesn't seem to allow this command. I am using cPanel phpmyadmin for reference.

I am definitely no expert in PHP or SQL so please be kind lol. Also sorry about some of the dodgy indentation.

SaltyJane
  • 1
  • 1
  • 2
    You could just have a `LoginAttempts` table that stores username, date/time, success/fail and maybe IP and UserAgent. When someone logs in, just count the number of failed login attempts for the past 15 minutes. No need to store anything in the `User` table itself – Chris Haas Oct 07 '22 at 14:25
  • Sorry I think I've explained it badly. I can already lockout the user, I am just trying to unlock their account after 15 minutes. I already have a table for failed login attempts which I am checking in order to lock them in the first place – SaltyJane Oct 07 '22 at 14:28
  • 2
    You explained really well, I was just trying to offer a way to greatly (IMHO) simplify the whole thing. A user isn't in a "locked out" state, at least at a glance. Instead, when you login, you check if that login should be considered locked out. – Chris Haas Oct 07 '22 at 14:40
  • Oh that makes sense sorry. If I can't find a way to unlock an account after a time delay I may do it this way. I want to be able to give the user a chance to wait and come back if they did just mistype 5 times, then if they have another 5 failed attempts lock them out permanently and they will have to contact us to reset their password. – SaltyJane Oct 07 '22 at 14:47
  • 1
    I agree with @ChrisHaas, creating scheduled events in mysql on the fly is not a great idea. Derived the information from the past account activity. Or just store when the lockout started and compare time when the user wants to do something if they are still blocked or not. – Shadow Oct 07 '22 at 14:49
  • @Shadow Oh that's a good idea re storing the time and comparing it, thank you I hadn't thought of this. May I ask if there is a particular reason why creating events on the fly isn't a good idea? I'm relatively new to SQL (only really used basic CRUD statements) and absolutely hate it, despite its usefulness! – SaltyJane Oct 07 '22 at 14:54
  • I see similar problem with [PDO](https://stackoverflow.com/questions/64363161/create-event-statement-not-working-with-pdo), the answer described: _The section on [prepared statements](https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html) in mysql manual contains the list of SQL statements that you use in a prepared statement. `CREATE EVENT` statement is not included in this list, so you cannot use it as a prepared statement._ – vee Oct 07 '22 at 14:56
  • I also like @Shadow's suggestion of the date/time locked out. That makes it really easy for an admin to clear, too. – Chris Haas Oct 07 '22 at 14:57
  • @SaltyJane Imagine how creating scheduled events for locked-out users would scale for thousands of users! – Shadow Oct 07 '22 at 15:24
  • @Shadow Oh I see thank you. I often forget to consider performance if the application was scaled out as I've only had the need for up to 8 users! I am trying to keep it in mind – SaltyJane Oct 07 '22 at 17:34

0 Answers0