mirror of
https://github.com/cp6/my-idlers.git
synced 2024-11-16 23:42:32 +01:00
commit
955e3de89f
4
AUTH.php
Normal file
4
AUTH.php
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
require_once('class.php');
|
||||||
|
$create_account = new auth();
|
||||||
|
$create_account->selfDestruct();
|
@ -11,7 +11,8 @@ V2 build is going to be a complete overhaul of My idlers with the project being
|
|||||||
Despite what the name infers this self hosted web app isn't just for storing idling server information.
|
Despite what the name infers this self hosted web app isn't just for storing idling server information.
|
||||||
By using a [YABs](https://github.com/masonr/yet-another-bench-script) output you can get disk & network speed values along with GeekBench 5 scores to do easier comparing and sorting.
|
By using a [YABs](https://github.com/masonr/yet-another-bench-script) output you can get disk & network speed values along with GeekBench 5 scores to do easier comparing and sorting.
|
||||||
|
|
||||||
[![Generic badge](https://img.shields.io/badge/version-1.4-blue.svg)](https://shields.io/)
|
|
||||||
|
[![Generic badge](https://img.shields.io/badge/version-1.5-blue.svg)](https://shields.io/)
|
||||||
|
|
||||||
## 1.4 changes:
|
## 1.4 changes:
|
||||||
**If you have version 1.3 already installed please run ```update1.3to1.4.sql```**
|
**If you have version 1.3 already installed please run ```update1.3to1.4.sql```**
|
||||||
|
11
calls.php
11
calls.php
@ -2,8 +2,10 @@
|
|||||||
header('Access-Control-Allow-Origin: *');
|
header('Access-Control-Allow-Origin: *');
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
require_once('class.php');
|
require_once('class.php');
|
||||||
|
$auth = new auth();
|
||||||
|
$auth->sessionStartIfNone();
|
||||||
|
if (isset($_SESSION['token'])) {
|
||||||
$idle = new idlers();
|
$idle = new idlers();
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
||||||
if (isset($_GET['type'])) {
|
if (isset($_GET['type'])) {
|
||||||
if ($_GET['type'] == 'server') {
|
if ($_GET['type'] == 'server') {
|
||||||
@ -98,3 +100,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
|
|||||||
die();
|
die();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
header('HTTP/1.1 401 Unauthorized');
|
||||||
|
echo json_encode(array(
|
||||||
|
'code' => 401,
|
||||||
|
'message' => 'Unauthorized'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
261
class.php
261
class.php
@ -23,6 +23,11 @@ class idlersConfig
|
|||||||
const SAVE_YABS_OUTPUT = true;//true or false
|
const SAVE_YABS_OUTPUT = true;//true or false
|
||||||
|
|
||||||
const GET_ASN_INFO = true;//Get ANS name and number
|
const GET_ASN_INFO = true;//Get ANS name and number
|
||||||
|
|
||||||
|
//Failed attempts before ip locked from attempting login
|
||||||
|
const FAIL_ATTEMPTS_ALLOWED = 4;
|
||||||
|
//Minutes to lock ip for
|
||||||
|
const IP_LOCK_MINUTES = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
class elementHelpers extends idlersConfig
|
class elementHelpers extends idlersConfig
|
||||||
@ -1403,7 +1408,7 @@ class idlers extends helperFunctions
|
|||||||
$this->colOpen('col-12 col-md-6 mm-col');
|
$this->colOpen('col-12 col-md-6 mm-col');
|
||||||
$this->tagOpen('div', 'input-group');
|
$this->tagOpen('div', 'input-group');
|
||||||
$this->inputPrepend('Bandwidth');
|
$this->inputPrepend('Bandwidth');
|
||||||
$this->numberInput('me_bandwidth', '', 'form-control', false, 1, 9999);
|
$this->numberInput('me_bandwidth', '', 'form-control', false, 0, 9999);
|
||||||
$this->outputString('<div class="input-group-append"><span class="input-group-text">TB</span></div>');
|
$this->outputString('<div class="input-group-append"><span class="input-group-text">TB</span></div>');
|
||||||
$this->tagClose('div', 3);
|
$this->tagClose('div', 3);
|
||||||
|
|
||||||
@ -1431,7 +1436,7 @@ class idlers extends helperFunctions
|
|||||||
$this->rowColOpen('form-row', 'col-6');
|
$this->rowColOpen('form-row', 'col-6');
|
||||||
$this->tagOpen('div', 'input-group');
|
$this->tagOpen('div', 'input-group');
|
||||||
$this->inputPrepend('Swap');
|
$this->inputPrepend('Swap');
|
||||||
$this->numberInput('me_swap', '', 'form-control', false, 0.5, 9999, 'any');
|
$this->numberInput('me_swap', '', 'form-control', false, 0, 9999, 'any');
|
||||||
$this->tagClose('div', 2);
|
$this->tagClose('div', 2);
|
||||||
$this->colOpen('col-6');
|
$this->colOpen('col-6');
|
||||||
$this->tagOpen('div', 'input-group');
|
$this->tagOpen('div', 'input-group');
|
||||||
@ -1541,7 +1546,7 @@ class idlers extends helperFunctions
|
|||||||
$this->rowColOpen('form-row', 'col-12');
|
$this->rowColOpen('form-row', 'col-12');
|
||||||
$this->tagOpen('div', 'input-group');
|
$this->tagOpen('div', 'input-group');
|
||||||
$this->inputPrepend('Bandwidth');
|
$this->inputPrepend('Bandwidth');
|
||||||
$this->numberInput('sh_me_bandwidth', '', 'form-control', false, 1, 99999);
|
$this->numberInput('sh_me_bandwidth', '', 'form-control', false, 0, 99999);
|
||||||
$this->outputString('<div class="input-group-append"><span class="input-group-text">GB</span></div>');
|
$this->outputString('<div class="input-group-append"><span class="input-group-text">GB</span></div>');
|
||||||
$this->tagClose('div', 3);
|
$this->tagClose('div', 3);
|
||||||
|
|
||||||
@ -1928,7 +1933,7 @@ class idlers extends helperFunctions
|
|||||||
$this->colOpen('col-12 col-md-3');
|
$this->colOpen('col-12 col-md-3');
|
||||||
$this->tagOpen('div', 'input-group');
|
$this->tagOpen('div', 'input-group');
|
||||||
$this->inputPrepend('Swap');
|
$this->inputPrepend('Swap');
|
||||||
$this->numberInput('swap', '100', 'form-control', true, 1, 62000, 'any');
|
$this->numberInput('swap', '100', 'form-control', true, 0, 62000, 'any');
|
||||||
$this->tagClose('div', 2);
|
$this->tagClose('div', 2);
|
||||||
$this->colOpen('col-12 col-md-3');
|
$this->colOpen('col-12 col-md-3');
|
||||||
$this->tagOpen('div', 'input-group');
|
$this->tagOpen('div', 'input-group');
|
||||||
@ -3847,3 +3852,251 @@ class itemUpdate extends idlers
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class auth extends idlers
|
||||||
|
{
|
||||||
|
public string $token;
|
||||||
|
private string $user;
|
||||||
|
private string $ip_address;
|
||||||
|
|
||||||
|
public function selfDestruct()
|
||||||
|
{
|
||||||
|
$select = $this->dbConnect()->prepare("SELECT `user` FROM `auth`;");
|
||||||
|
$select->execute();
|
||||||
|
$user_count = $select->rowCount();
|
||||||
|
if ($user_count == 0) {
|
||||||
|
//Begin create user
|
||||||
|
$this->createAccountForm();
|
||||||
|
} else {
|
||||||
|
//User already exists
|
||||||
|
//Delete AUTH.php
|
||||||
|
$this->deleteAuthFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createAccountForm()
|
||||||
|
{
|
||||||
|
if (isset($_POST['pass']) && isset($_POST['user'])) {
|
||||||
|
//Form submitted
|
||||||
|
$this->insertAccount($_POST['user'], $_POST['pass']);
|
||||||
|
} else {
|
||||||
|
$this->pageHead();
|
||||||
|
$this->rowColOpen('row text-center', 'col-12');
|
||||||
|
$this->tagOpen('div', 'card');
|
||||||
|
$this->tagOpen('div', 'card-header');
|
||||||
|
$this->HTMLphrase('h1', '', 'Create account');
|
||||||
|
$this->HTMLphrase('p', '', 'If you are seeing this there are currently 0 accounts. Once 1 is created this file gets deleted.');
|
||||||
|
$this->tagClose('div');
|
||||||
|
$this->tagOpen('div', 'card-body');
|
||||||
|
$this->outputString('<form method="post">');
|
||||||
|
$this->rowColOpen('form-row', 'col-12 col-md-6 mm-col');
|
||||||
|
$this->tagOpen('div', 'input-group');
|
||||||
|
$this->inputPrepend('Username');
|
||||||
|
$this->textInput('user', '', 'form-control', true, 4, 64);
|
||||||
|
$this->tagClose('div', 2);
|
||||||
|
$this->colOpen('col-12 col-md-6 mm-col');
|
||||||
|
$this->tagOpen('div', 'input-group');
|
||||||
|
$this->inputPrepend('Password');
|
||||||
|
$this->outputString("<input type='password' id='pass' name='pass' class='form-control' min-length='12' max-length='124' required>");
|
||||||
|
$this->tagClose('div', 3);
|
||||||
|
$this->rowColOpen('form-row text-center', 'col-12');
|
||||||
|
$this->submitInput('Create', 'submitInput', 'btn btn-main');
|
||||||
|
$this->tagClose('div', 2);
|
||||||
|
$this->tagClose('form');
|
||||||
|
$this->tagClose('div', 4);
|
||||||
|
$this->pageFooter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function insertAccount(string $user, string $pass): bool
|
||||||
|
{
|
||||||
|
$hashed_password = password_hash($pass, PASSWORD_DEFAULT);//Hash the submitted password
|
||||||
|
$insert = $this->dbConnect()->prepare("INSERT INTO `auth` (`user`, `pass`) VALUES (?,?)");
|
||||||
|
return $insert->execute([$user, $hashed_password]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sessionStartIfNone()
|
||||||
|
{
|
||||||
|
if (session_status() == PHP_SESSION_NONE) {
|
||||||
|
session_start();//No session stated... so start one
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isLoggedIn(): bool
|
||||||
|
{
|
||||||
|
$this->sessionStartIfNone();//Start session if none already started
|
||||||
|
if (isset($_SESSION['token']) && !empty($_SESSION['token'])) {
|
||||||
|
$this->token = $_SESSION['token'];
|
||||||
|
return true;//Logged in
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loginForm()
|
||||||
|
{
|
||||||
|
if (isset($_POST['user']) && isset($_POST['pass'])) {
|
||||||
|
$this->attemptLogin($_POST['user'], $_POST['pass']);
|
||||||
|
} else {
|
||||||
|
$this->pageHead();
|
||||||
|
$this->rowColOpen('row text-center', 'col-12');
|
||||||
|
$this->tagOpen('div', 'card');
|
||||||
|
$this->tagOpen('div', 'card-header');
|
||||||
|
$this->HTMLphrase('h3', '', 'My idlers login');
|
||||||
|
$this->tagClose('div');
|
||||||
|
$this->tagOpen('div', 'card-body');
|
||||||
|
$this->outputString('<form method="post">');
|
||||||
|
$this->rowColOpen('form-row', 'col-12 col-md-3');
|
||||||
|
$this->tagClose('div', 1);
|
||||||
|
$this->colOpen('col-12 col-md-6 mm-col');
|
||||||
|
$this->tagOpen('div', 'input-group');
|
||||||
|
$this->inputPrepend('Username');
|
||||||
|
$this->textInput('user', '', 'form-control', true, 4, 64);
|
||||||
|
$this->tagClose('div', 2);
|
||||||
|
$this->colOpen('col-12 col-md-3');
|
||||||
|
$this->tagClose('div', 2);
|
||||||
|
|
||||||
|
$this->rowColOpen('form-row', 'col-12 col-md-3');
|
||||||
|
$this->tagClose('div', 1);
|
||||||
|
$this->colOpen('col-12 col-md-6 mm-col');
|
||||||
|
$this->tagOpen('div', 'input-group');
|
||||||
|
$this->inputPrepend('Password');
|
||||||
|
$this->outputString("<input type='password' id='pass' name='pass' class='form-control' min-length='12' max-length='124' required>");
|
||||||
|
$this->tagClose('div', 2);
|
||||||
|
$this->colOpen('col-12 col-md-3');
|
||||||
|
$this->tagClose('div', 2);
|
||||||
|
|
||||||
|
$this->rowColOpen('form-row text-center', 'col-12');
|
||||||
|
$this->submitInput('Login', 'submitInput', 'btn btn-main');
|
||||||
|
$this->tagClose('div', 2);
|
||||||
|
$this->tagClose('form');
|
||||||
|
$this->tagClose('div', 4);
|
||||||
|
$this->pageFooter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function usernameExists(string $username): bool
|
||||||
|
{
|
||||||
|
$select = $this->dbConnect()->prepare("SELECT `user` FROM `auth` WHERE `user` = ? LIMIT 1;");
|
||||||
|
$select->execute([$username]);
|
||||||
|
$row = $select->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (!empty($row)) {//Row found
|
||||||
|
$this->user = $row['user'];
|
||||||
|
return true;
|
||||||
|
} else {//NO row found
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkPasswordCorrect(string $password): bool
|
||||||
|
{
|
||||||
|
$select = $this->dbConnect()->prepare("SELECT `pass` FROM `auth` WHERE `user` = ? LIMIT 1;");
|
||||||
|
$select->execute([$this->user]);
|
||||||
|
$row = $select->fetch(PDO::FETCH_ASSOC);
|
||||||
|
if (password_verify($password, $row['pass'])) {
|
||||||
|
return true;//Password is correct
|
||||||
|
} else {
|
||||||
|
return false;//Bad password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function doLoginWasSuccess(): bool
|
||||||
|
{
|
||||||
|
$update = $this->dbConnect()->prepare("UPDATE `auth` SET `login_count` = (login_count + 1), `last_login` = NOW() WHERE `user` = ? LIMIT 1;");
|
||||||
|
return $update->execute([$this->user]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addLoginFailCount(): bool
|
||||||
|
{
|
||||||
|
$update = $this->dbConnect()->prepare("UPDATE `auth` SET login_fails = (login_fails + 1), `last_fail` = NOW() WHERE `user` = ? LIMIT 1;");
|
||||||
|
return $update->execute([$this->user]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function addLoginFailAttempt(): bool
|
||||||
|
{
|
||||||
|
$insert = $this->dbConnect()->prepare('INSERT IGNORE INTO `login_attempts` (`user`, `ip`) VALUES (?, ?)');
|
||||||
|
return $insert->execute([$this->user, $this->ip_address]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setToken(int $length = 32)
|
||||||
|
{
|
||||||
|
$this->sessionStartIfNone();
|
||||||
|
$_SESSION['token'] = $this->genID($length);//Set session as token
|
||||||
|
$this->token = $_SESSION['token'];
|
||||||
|
$update_token = $this->dbConnect()->prepare("UPDATE `auth` SET `token` = ? WHERE `user` = ? LIMIT 1;");
|
||||||
|
$update_token->execute([$_SESSION['token'], $this->user]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function indexRedirect()
|
||||||
|
{
|
||||||
|
header("Location: index.php");
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRecentFailCount(): int
|
||||||
|
{
|
||||||
|
$select = $this->dbConnect()->prepare("SELECT COUNT(*) as the_count FROM `login_attempts` WHERE `ip` = ? AND `datetime` > (NOW() - INTERVAL 10 MINUTE);");
|
||||||
|
$select->execute([$this->ip_address]);
|
||||||
|
return $select->fetch()['the_count'];//login fails for IP in last 10 minutes
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasLockTimePassed(): bool
|
||||||
|
{
|
||||||
|
$select = $this->dbConnect()->prepare("SELECT `datetime` FROM `login_attempts` WHERE `ip` = ? ORDER BY `datetime` DESC LIMIT 1;");
|
||||||
|
$select->execute([$this->ip_address]);
|
||||||
|
$locked_until = $select->fetch(PDO::FETCH_ASSOC);
|
||||||
|
$time = new DateTime($locked_until['datetime']);
|
||||||
|
$time->add(new DateInterval("PT" . self::IP_LOCK_MINUTES . "M"));
|
||||||
|
$locked_until_formatted = $time->format('Y-m-d H:i:s');
|
||||||
|
$now = date('Y-m-d H:i:s');
|
||||||
|
if ($now > $locked_until_formatted) {//Time has passed
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function attemptLogin(string $username, string $password)
|
||||||
|
{
|
||||||
|
$this->ip_address = $_SERVER['REMOTE_ADDR'];
|
||||||
|
if ($this->getRecentFailCount() >= self::FAIL_ATTEMPTS_ALLOWED) {
|
||||||
|
if (!$this->hasLockTimePassed()) {//IP is currently not allowed to attempt login
|
||||||
|
$this->indexRedirect();//Redirect to index and show login form
|
||||||
|
} else {//IP has passed lock time
|
||||||
|
if ($this->usernameExists($username)) {//Username found
|
||||||
|
if ($this->checkPasswordCorrect($password)) {//Password is correct
|
||||||
|
$this->doLoginWasSuccess();//Add login counter and last login datetime
|
||||||
|
$this->setToken();//Set session token
|
||||||
|
$this->indexRedirect();//Redirect to index and show servers
|
||||||
|
} else {//Password is wrong
|
||||||
|
$this->addLoginFailCount();
|
||||||
|
$this->addLoginFailAttempt();
|
||||||
|
$this->indexRedirect();//Redirect to index and login form
|
||||||
|
}
|
||||||
|
} else {//Username not found in DB
|
||||||
|
$this->indexRedirect();//Redirect to index and show login form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {//A clean attempt
|
||||||
|
echo $this->getRecentFailCount();
|
||||||
|
if ($this->usernameExists($username)) {//Username found
|
||||||
|
if ($this->checkPasswordCorrect($password)) {//Password is correct
|
||||||
|
$this->doLoginWasSuccess();//Add login counter and last login datetime
|
||||||
|
$this->setToken();//Set session token
|
||||||
|
$this->indexRedirect();//Redirect to index and show servers
|
||||||
|
} else {//Password is wrong
|
||||||
|
$this->addLoginFailCount();//Add 1 onto login fail count
|
||||||
|
$this->addLoginFailAttempt();//ip and datetime into login attempt fail logs
|
||||||
|
$this->indexRedirect();//Redirect to index and show login form
|
||||||
|
}
|
||||||
|
} else {//Username not found in DB
|
||||||
|
$this->indexRedirect();//Redirect to index and show login form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function deleteAuthFile()
|
||||||
|
{
|
||||||
|
unlink('AUTH.php');
|
||||||
|
}
|
||||||
|
}
|
@ -394,6 +394,27 @@ CREATE TABLE IF NOT EXISTS `speed_tests`
|
|||||||
/*!40000 ALTER TABLE `speed_tests`
|
/*!40000 ALTER TABLE `speed_tests`
|
||||||
ENABLE KEYS */;
|
ENABLE KEYS */;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `auth`
|
||||||
|
(
|
||||||
|
`user` varchar(64) NOT NULL,
|
||||||
|
`pass` varchar(255) DEFAULT NULL,
|
||||||
|
`token` char(32) DEFAULT NULL,
|
||||||
|
`login_count` int(11) DEFAULT 0,
|
||||||
|
`login_fails` int(11) DEFAULT 0,
|
||||||
|
`last_login` datetime DEFAULT NULL,
|
||||||
|
`last_fail` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`user`)
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `login_attempts`
|
||||||
|
(
|
||||||
|
`user` varchar(124) DEFAULT NULL,
|
||||||
|
`ip` varchar(124) DEFAULT NULL,
|
||||||
|
`datetime` datetime DEFAULT current_timestamp()
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4;
|
||||||
|
|
||||||
/*!40101 SET SQL_MODE = IFNULL(@OLD_SQL_MODE, '') */;
|
/*!40101 SET SQL_MODE = IFNULL(@OLD_SQL_MODE, '') */;
|
||||||
/*!40014 SET FOREIGN_KEY_CHECKS = IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
|
/*!40014 SET FOREIGN_KEY_CHECKS = IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
|
||||||
/*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */;
|
/*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE `servers`
|
|
||||||
ADD COLUMN `asn` VARCHAR(124) NULL DEFAULT NULL AFTER `notes`;
|
|
20
update1.4to1.5.sql
Normal file
20
update1.4to1.5.sql
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `auth`
|
||||||
|
(
|
||||||
|
`user` varchar(64) NOT NULL,
|
||||||
|
`pass` varchar(255) DEFAULT NULL,
|
||||||
|
`token` char(32) DEFAULT NULL,
|
||||||
|
`login_count` int(11) DEFAULT 0,
|
||||||
|
`login_fails` int(11) DEFAULT 0,
|
||||||
|
`last_login` datetime DEFAULT NULL,
|
||||||
|
`last_fail` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`user`)
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `login_attempts`
|
||||||
|
(
|
||||||
|
`user` varchar(124) DEFAULT NULL,
|
||||||
|
`ip` varchar(124) DEFAULT NULL,
|
||||||
|
`datetime` datetime DEFAULT current_timestamp()
|
||||||
|
) ENGINE = InnoDB
|
||||||
|
DEFAULT CHARSET = utf8mb4;
|
Loading…
Reference in New Issue
Block a user