mirror of
https://github.com/slawkens/myaac.git
synced 2025-10-19 04:03:26 +02:00
Merge branch 'develop' into feature/refactor-account-lost
This commit is contained in:
@@ -105,4 +105,8 @@ $config['clients'] = [
|
||||
1316,
|
||||
1320,
|
||||
1321,
|
||||
1322,
|
||||
1330,
|
||||
1332,
|
||||
1340,
|
||||
];
|
||||
|
@@ -1041,7 +1041,7 @@ function load_config_lua($filename)
|
||||
return $result;
|
||||
}
|
||||
|
||||
function str_replace_first($search, $replace, $subject) {
|
||||
function str_replace_first($search,$replace, $subject) {
|
||||
$pos = strpos($subject, $search);
|
||||
if ($pos !== false) {
|
||||
return substr_replace($subject, $replace, $pos, strlen($search));
|
||||
|
@@ -184,8 +184,14 @@ abstract class OTS_Base_DB extends PDO implements IOTS_DB
|
||||
$query = 'UPDATE '.$this->tableName($table).' SET ';
|
||||
|
||||
$count = count($fields);
|
||||
for ($i = 0; $i < $count; $i++)
|
||||
$query.= $this->fieldName($fields[$i]).' = '.$this->quote($values[$i]).', ';
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$value = 'NULL';
|
||||
if ($values[$i] !== null) {
|
||||
$value = $this->quote($values[$i]);
|
||||
}
|
||||
|
||||
$query.= $this->fieldName($fields[$i]).' = '.$value.', ';
|
||||
}
|
||||
|
||||
$query = substr($query, 0, -2);
|
||||
$query.=' WHERE (';
|
||||
|
@@ -60,12 +60,7 @@ class OTS_House extends OTS_Row_DAO
|
||||
private $tiles = array();
|
||||
|
||||
public function load($id) {
|
||||
$this->data = $this->db->query('SELECT * FROM `houses` WHERE `id` = ' . $id )->fetch();
|
||||
foreach($this->data as $key => $value) {
|
||||
if(is_numeric($key)) {
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
}
|
||||
$this->data = $this->db->query('SELECT * FROM `houses` WHERE `id` = ' . $id )->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function find($name)
|
||||
|
@@ -2,6 +2,10 @@
|
||||
|
||||
use MyAAC\Settings;
|
||||
|
||||
if (!$db->hasTable('players')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = $db->query("SELECT `id` FROM `players` WHERE (`name` = " . $db->quote("Rook Sample") . " OR `name` = " . $db->quote("Sorcerer Sample") . " OR `name` = " . $db->quote("Druid Sample") . " OR `name` = " . $db->quote("Paladin Sample") . " OR `name` = " . $db->quote("Knight Sample") . " OR `name` = " . $db->quote("Account Manager") . ") ORDER BY `id`;");
|
||||
|
||||
$highscores_ignored_ids = array();
|
||||
|
@@ -148,6 +148,10 @@ if($save)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* two hooks for compatibility
|
||||
*/
|
||||
$hooks->trigger(HOOK_ACCOUNT_CREATE_AFTER_SUBMIT, $params);
|
||||
if (!$hooks->trigger(HOOK_ACCOUNT_CREATE_POST, $params)) {
|
||||
return;
|
||||
}
|
||||
@@ -187,6 +191,8 @@ if($save)
|
||||
$new_account->setEMail($email);
|
||||
$new_account->save();
|
||||
|
||||
$hooks->trigger(HOOK_ACCOUNT_CREATE_AFTER_SAVED, ['account' => $new_account]);
|
||||
|
||||
if(USE_ACCOUNT_SALT)
|
||||
$new_account->setCustomField('salt', $salt);
|
||||
|
||||
|
@@ -8,6 +8,9 @@
|
||||
* @copyright 2023 MyAAC
|
||||
* @link https://my-aac.org
|
||||
*/
|
||||
|
||||
use MyAAC\RateLimit;
|
||||
|
||||
defined('MYAAC') or die('Direct access not allowed!');
|
||||
|
||||
// new login with data from form
|
||||
@@ -18,30 +21,13 @@ if($logged || !isset($_POST['account_login']) || !isset($_POST['password_login']
|
||||
$login_account = $_POST['account_login'];
|
||||
$login_password = $_POST['password_login'];
|
||||
$remember_me = isset($_POST['remember_me']);
|
||||
$ip = get_browser_real_ip();
|
||||
if(!empty($login_account) && !empty($login_password))
|
||||
{
|
||||
if($cache->enabled())
|
||||
{
|
||||
$tmp = '';
|
||||
if($cache->fetch('failed_logins', $tmp))
|
||||
{
|
||||
$tmp = unserialize($tmp);
|
||||
$to_remove = array();
|
||||
foreach($tmp as $ip => $t)
|
||||
{
|
||||
if(time() - $t['last'] >= 5 * 60)
|
||||
$to_remove[] = $ip;
|
||||
}
|
||||
|
||||
foreach($to_remove as $ip)
|
||||
unset($tmp[$ip]);
|
||||
}
|
||||
else
|
||||
$tmp = array();
|
||||
|
||||
$ip = $_SERVER['REMOTE_ADDR'];
|
||||
$t = $tmp[$ip] ?? null;
|
||||
}
|
||||
$limiter = new RateLimit('failed_logins', setting('core.account_login_attempts_limit'), setting('core.account_login_ban_time'));
|
||||
$limiter->enabled = setting('core.account_login_ipban_protection');
|
||||
$limiter->load();
|
||||
|
||||
$account_logged = new OTS_Account();
|
||||
if (config('account_login_by_email')) {
|
||||
@@ -56,14 +42,12 @@ if(!empty($login_account) && !empty($login_password))
|
||||
}
|
||||
}
|
||||
|
||||
if($account_logged->isLoaded() && encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password) == $account_logged->getPassword()
|
||||
&& (!isset($t) || $t['attempts'] < 5)
|
||||
if($account_logged->isLoaded() && encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password) == $account_logged->getPassword() && ($limiter->enabled && !$limiter->exceeded($ip))
|
||||
)
|
||||
{
|
||||
if (setting('core.account_mail_verify') && (int)$account_logged->getCustomField('email_verified') !== 1) {
|
||||
$errors[] = 'Your account is not verified. Please verify your email address. If the message is not coming check the SPAM folder in your E-Mail client.';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
session_regenerate_id();
|
||||
setSession('account', $account_logged->getId());
|
||||
setSession('password', encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password));
|
||||
@@ -87,38 +71,21 @@ if(!empty($login_account) && !empty($login_password))
|
||||
|
||||
$hooks->trigger(HOOK_LOGIN, array('account' => $account_logged, 'password' => $login_password, 'remember_me' => $remember_me));
|
||||
}
|
||||
|
||||
$limiter->reset($ip);
|
||||
}
|
||||
else
|
||||
{
|
||||
$hooks->trigger(HOOK_LOGIN_ATTEMPT, array('account' => $login_account, 'password' => $login_password, 'remember_me' => $remember_me));
|
||||
|
||||
$errorMessage = getAccountLoginByLabel() . ' or password is not correct.';
|
||||
$limiter->increment($ip);
|
||||
if ($limiter->exceeded($ip)) {
|
||||
$errorMessage = 'A wrong password has been entered ' . $limiter->max_attempts . ' times in a row. You are unable to log into your account for the next ' . $limiter->ttl . ' minutes. Please wait.';
|
||||
}
|
||||
|
||||
// temporary solution for blocking failed login attempts
|
||||
if($cache->enabled())
|
||||
{
|
||||
if(isset($t))
|
||||
{
|
||||
$t['attempts']++;
|
||||
$t['last'] = time();
|
||||
|
||||
if($t['attempts'] >= 5)
|
||||
$errors[] = 'A wrong password has been entered 5 times in a row. You are unable to log into your account for the next 5 minutes. Please wait.';
|
||||
else
|
||||
$errors[] = $errorMessage;
|
||||
}
|
||||
else
|
||||
{
|
||||
$t = array('attempts' => 1, 'last' => time());
|
||||
$errors[] = $errorMessage;
|
||||
}
|
||||
|
||||
$tmp[$ip] = $t;
|
||||
$cache->set('failed_logins', serialize($tmp), 60 * 60); // save for 1 hour
|
||||
}
|
||||
else {
|
||||
$errors[] = $errorMessage;
|
||||
}
|
||||
$errors[] = $errorMessage;
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@@ -180,7 +180,7 @@ if (empty($highscores)) {
|
||||
} else if ($skill == SKILL_FRAGS) // frags
|
||||
{
|
||||
if ($db->hasTable('player_killers')) {
|
||||
$query->addSelect(['value' => PlayerKillers::where('player_killers.player_id', 'players.id')->selectRaw('COUNT(*)')]);
|
||||
$query->addSelect(['value' => PlayerKillers::whereColumn('player_killers.player_id', 'players.id')->selectRaw('COUNT(*)')]);
|
||||
} else {
|
||||
$query->addSelect(['value' => PlayerDeath::unjustified()->whereColumn('player_deaths.killed_by', 'players.name')->selectRaw('COUNT(*)')]);
|
||||
}
|
||||
|
@@ -15,8 +15,7 @@ use MyAAC\Settings;
|
||||
|
||||
return [
|
||||
'name' => 'MyAAC',
|
||||
'settings' =>
|
||||
[
|
||||
'settings' => [
|
||||
[
|
||||
'type' => 'category',
|
||||
'title' => 'General'
|
||||
@@ -1374,7 +1373,7 @@ Sent by MyAAC,<br/>
|
||||
'name' => 'Item Images URL',
|
||||
'type' => 'text',
|
||||
'desc' => 'Set to <strong>images/items</strong> if you host your own items in images folder',
|
||||
'default' => 'http://item-images.ots.me/1092/',
|
||||
'default' => 'https://item-images.ots.me/1092/',
|
||||
],
|
||||
'item_images_extension' => [
|
||||
'name' => 'Item Images File Extension',
|
||||
@@ -1390,7 +1389,7 @@ Sent by MyAAC,<br/>
|
||||
'name' => 'Outfit Images URL',
|
||||
'type' => 'text',
|
||||
'desc' => 'Set to animoutfit.php for animated outfit',
|
||||
'default' => 'http://outfit-images.ots.me/outfit.php',
|
||||
'default' => 'https://outfit-images.ots.me/outfit.php',
|
||||
],
|
||||
'outfit_images_wrong_looktypes' => [
|
||||
'name' => 'Outfit Images Wrong Looktypes',
|
||||
@@ -1590,6 +1589,34 @@ Sent by MyAAC,<br/>
|
||||
'account_change_character_sex', '=', 'true',
|
||||
],
|
||||
],
|
||||
[
|
||||
'type' => 'category',
|
||||
'title' => 'Security',
|
||||
],
|
||||
[
|
||||
'type' => 'section',
|
||||
'title' => 'IP Ban Protection',
|
||||
],
|
||||
'account_login_ipban_protection' => [
|
||||
'name' => 'IP Ban Protection',
|
||||
'type' => 'boolean',
|
||||
'desc' => 'Activate IP ban protection after exceeding incorrect login attempts',
|
||||
'default' => true,
|
||||
],
|
||||
|
||||
'account_login_attempts_limit' => [
|
||||
'name' => 'Login Attempts Limit',
|
||||
'type' => 'number',
|
||||
'desc' => 'Number of incorrect login attempts before banning the IP',
|
||||
'default' => 5, // Ajuste conforme necessário
|
||||
],
|
||||
|
||||
'account_login_ban_time' => [
|
||||
'name' => 'Ban Time (Minutes)',
|
||||
'type' => 'number',
|
||||
'desc' => 'Time in minutes the IP will be banned after exceeding login attempts',
|
||||
'default' => 30, // Ajuste conforme necessário
|
||||
],
|
||||
],
|
||||
'callbacks' => [
|
||||
'beforeSave' => function(&$settings, &$values) {
|
||||
@@ -1658,6 +1685,6 @@ Sent by MyAAC,<br/>
|
||||
|
||||
return $success;
|
||||
},
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
|
@@ -267,7 +267,7 @@ class CreateCharacter
|
||||
[
|
||||
'account' => $account,
|
||||
'player' => $player,
|
||||
'samplePlayer' => $playerSample,
|
||||
'playerSample' => $playerSample,
|
||||
'name' => $name,
|
||||
'sex' => $sex,
|
||||
'vocation' => $vocation,
|
||||
|
@@ -5,7 +5,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PlayerKillers extends Model {
|
||||
|
||||
protected $table = 'players_killers';
|
||||
protected $table = 'player_killers';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
|
120
system/src/RateLimit.php
Normal file
120
system/src/RateLimit.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace MyAAC;
|
||||
|
||||
|
||||
class RateLimit
|
||||
{
|
||||
public string $key;
|
||||
public int $max_attempts;
|
||||
public int $ttl;
|
||||
public $enabled = false;
|
||||
protected array $data;
|
||||
|
||||
public function __construct(string $key, int $max_attempts, int $ttl)
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->max_attempts = $max_attempts;
|
||||
$this->ttl = $ttl;
|
||||
}
|
||||
|
||||
public function attempts(string $ip): int
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isset($this->data[$ip]['attempts'])) {
|
||||
return $this->data[$ip]['attempts'];
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function exceeded(string $ip): bool {
|
||||
if (!$this->enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->attempts($ip) >= $this->max_attempts;
|
||||
}
|
||||
|
||||
public function increment(string $ip): bool
|
||||
{
|
||||
global $cache;
|
||||
if ($this->enabled && $cache->enabled()) {
|
||||
if (isset($this->data[$ip]['attempts']) && isset($this->data[$ip]['last'])) {
|
||||
$this->data[$ip]['attempts']++;
|
||||
$this->data[$ip]['last'] = time();
|
||||
} else {
|
||||
$this->data[$ip] = [
|
||||
'attempts' => 1,
|
||||
'last' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function reset(string $ip): void
|
||||
{
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($this->data[$ip])) {
|
||||
unset($this->data[$ip]);
|
||||
}
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function save(): void
|
||||
{
|
||||
global $cache;
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = $this->data;
|
||||
$cache->set($this->key, serialize($data), $this->ttl * 60);
|
||||
}
|
||||
|
||||
public function load(): void
|
||||
{
|
||||
global $cache;
|
||||
if (!$this->enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
if ($this->enabled && $cache->enabled()) {
|
||||
$tmp = '';
|
||||
if ($cache->fetch($this->key, $tmp)) {
|
||||
$data = unserialize($tmp);
|
||||
$to_remove = [];
|
||||
foreach ($data as $ip => $t) {
|
||||
if (time() - $t['last'] >= ($this->ttl * 60)) {
|
||||
$to_remove[] = $ip;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($to_remove)) {
|
||||
foreach ($to_remove as $ip) {
|
||||
unset($data[$ip]);
|
||||
}
|
||||
|
||||
$this->save();
|
||||
}
|
||||
} else {
|
||||
$data = [];
|
||||
}
|
||||
}
|
||||
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
@@ -45,6 +45,12 @@ define('HOOK_ACCOUNT_CREATE_AFTER_TOWNS', ++$i);
|
||||
define('HOOK_ACCOUNT_CREATE_BEFORE_SUBMIT_BUTTON', ++$i);
|
||||
define('HOOK_ACCOUNT_CREATE_AFTER_FORM', ++$i);
|
||||
define('HOOK_ACCOUNT_CREATE_POST', ++$i);
|
||||
define('HOOK_ACCOUNT_CREATE_AFTER_SUBMIT', ++$i);
|
||||
define('HOOK_ACCOUNT_CREATE_AFTER_SAVED', ++$i);
|
||||
define('HOOK_ACCOUNT_MANAGE_BEFORE_GENERAL_INFORMATION', ++$i);
|
||||
define('HOOK_ACCOUNT_MANAGE_BEFORE_PUBLIC_INFORMATION', ++$i);
|
||||
define('HOOK_ACCOUNT_MANAGE_BEFORE_ACCOUNT_LOGS', ++$i);
|
||||
define('HOOK_ACCOUNT_MANAGE_BEFORE_CHARACTERS', ++$i);
|
||||
define('HOOK_ACCOUNT_LOGIN_BEFORE_PAGE', ++$i);
|
||||
define('HOOK_ACCOUNT_LOGIN_BEFORE_ACCOUNT', ++$i);
|
||||
define('HOOK_ACCOUNT_LOGIN_AFTER_ACCOUNT', ++$i);
|
||||
|
@@ -142,10 +142,14 @@ function updateStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
$status['uptime'] = $serverStatus->getUptime();
|
||||
$h = floor($status['uptime'] / 3600);
|
||||
$m = floor(($status['uptime'] - $h * 3600) / 60);
|
||||
$status['uptimeReadable'] = $h . 'h ' . $m . 'm';
|
||||
$uptime = $status['uptime'] = $serverStatus->getUptime();
|
||||
$m = date('m', $uptime);
|
||||
$m = $m > 1 ? "$m months, " : ($m == 1 ? 'month, ' : '');
|
||||
$d = date('d', $uptime);
|
||||
$d = $d > 1 ? "$d days, " : ($d == 1 ? 'day, ' : '');
|
||||
$h = date('H', $uptime);
|
||||
$min = date('i', $uptime);
|
||||
$status['uptimeReadable'] = "{$m}{$d}{$h}h {$min}m";
|
||||
|
||||
$status['monsters'] = $serverStatus->getMonstersCount();
|
||||
$status['motd'] = $serverStatus->getMOTD();
|
||||
|
@@ -88,6 +88,7 @@
|
||||
</div>
|
||||
<br/><br/>
|
||||
{% endif %}
|
||||
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_GENERAL_INFORMATION') }}
|
||||
<a name="General+Information"></a>
|
||||
<h2>General Information</h2>
|
||||
<table width="100%">
|
||||
@@ -127,6 +128,7 @@
|
||||
{% endautoescape %}
|
||||
</table>
|
||||
<br/>
|
||||
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_PUBLIC_INFORMATION') }}
|
||||
<a name="Public+Information"></a>
|
||||
<h2>Public Information</h2>
|
||||
<table width="100%">
|
||||
@@ -145,6 +147,7 @@
|
||||
{% include('buttons.base.html.twig') %}
|
||||
</form>
|
||||
<br/>
|
||||
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_ACCOUNT_LOGS') }}
|
||||
<a name="Account+Logs" ></a>
|
||||
<h2>Action Log</h2>
|
||||
<table>
|
||||
@@ -164,6 +167,7 @@
|
||||
{% endautoescape %}
|
||||
</table>
|
||||
<br/>
|
||||
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_CHARACTERS') }}
|
||||
<a name="Characters" ></a>
|
||||
<h2>Character list: {{ players|length }} characters.</h2>
|
||||
<table>
|
||||
|
@@ -9,7 +9,13 @@
|
||||
<form action="{{ constant('BASE_URL') }}install/" method="post" autocomplete="off">
|
||||
<input type="hidden" name="step" id="step" value="finish" />
|
||||
|
||||
{% for value in ['email', 'account', 'password', 'password_confirm', 'player_name'] %}
|
||||
{% set values = ['email', 'account', 'password', 'password_confirm'] %}
|
||||
|
||||
{% if hasTablePlayers %}
|
||||
{% set values = values|merge(['player_name']) %}
|
||||
{% endif %}
|
||||
|
||||
{% for value in values %}
|
||||
|
||||
<div class="form-group mb-2">
|
||||
<label for="vars_{{ value }}">{{ locale['step_admin_' ~ value] }}</label>
|
||||
|
Reference in New Issue
Block a user