Compare commits

..

31 Commits

Author SHA1 Message Date
slawkens
9e0e2601d2 Add indexes to myaac_account_actions table 2026-04-12 14:13:13 +02:00
slawkens
e7198aeb23 Update CHANGELOG-2.x.md 2026-04-12 13:42:25 +02:00
slawkens
1fa3630f86 Update CHANGELOG-2.x.md 2026-04-12 13:33:16 +02:00
slawkens
e274b83504 Fix: Reset settings by plugin name, not the settings key name 2026-04-12 13:20:48 +02:00
slawkens
a467a540b1 Add constant DEFAULT_PRIORITY 2026-04-12 13:14:10 +02:00
slawkens
08507e2940 Plugins: type hints 2026-04-12 13:10:44 +02:00
slawkens
f1aa128408 Feat: plugins autoload init-priority option 2026-04-12 13:10:15 +02:00
slawkens
7104c2258f Settings: Add Reset button 2026-04-12 10:43:09 +02:00
slawkens
f51211d47a Merge branch 'main' into develop 2026-04-12 09:56:10 +02:00
slawkens
fa93187f80 Add $fillable to Account model 2026-04-12 09:56:01 +02:00
slawkens
2c62a97160 Make myaac_config table columns bigger 2026-04-12 08:53:02 +02:00
slawkens
7bc8a66cc1 BugTracker has been moved to plugins, remove the model 2026-04-11 18:13:25 +02:00
slawkens
f6c2e6e460 Merge branch 'main' into develop 2026-04-11 17:58:02 +02:00
slawkens
4145d9eb3c Fix: Clear hooks on plugin uninstall
Fixes error with gesior-shop-system clear-cache.php being called, despite it's removed
2026-04-11 17:49:22 +02:00
slawkens
a27b8a4fa5 Merge branch 'main' of https://github.com/slawkens/myaac 2026-04-11 17:42:45 +02:00
dependabot[bot]
4570ba3801 Bump lodash from 4.17.23 to 4.18.1 (#358)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.23 to 4.18.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-11 17:23:17 +02:00
slawkens
578c0548ee Start v1.8.10-dev 2026-04-11 15:23:46 +02:00
slawkens
aa63e1c986 Fix client boosted + online count (?) 2026-04-10 23:08:33 +02:00
slawkens
0413de85b5 Release v1.8.9 2026-04-06 12:23:42 +02:00
Slawomir Boczek
dd97a749b4 Better name validation, like in the original game website (#356)
* Better name validation, like in the original game website

* Don't automatically ucfirst and strtolower the cases of the word
    * This allows for names like: Lord of Ring, Man of the Earth etc.
* Don't allow special characters like: -, [], '
* Don't allow one letter words
* Require at least one vowel per word
* Add notice about admin logged in

* Add trim, for future

Currently its stripped anyway in the init.php, but AI don't know it :P

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Implement AI recommended changes

* Update tools/validate.php

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Trim $name

* Update Validator.php

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-06 10:41:29 +02:00
slawkens
050181357a Merge branch 'main' into develop 2026-03-15 13:04:37 +01:00
slawkens
4ae2fdd0df Add page load time to admin panel 2026-03-15 13:02:22 +01:00
slawkens
2bf5f5a1db Update GiveAdminCommand.php 2026-02-26 16:46:13 +01:00
slawkens
ccd70d2ee3 Merge branch 'main' into develop 2026-02-24 21:21:36 +01:00
slawkens
5fcde4708a Install: don't suggest deleting of install folder
It is not needed
Instead just remove ip.txt, this will lock the installation
2026-02-24 21:04:20 +01:00
slawkens
f15b0122c6 Nothing important, code style & grammar 2026-02-24 20:35:20 +01:00
slawkens
1da36c7f68 Merge branch 'main' into develop 2026-02-24 20:14:28 +01:00
slawkens
4eb7f48fd7 Update discord link 2026-02-24 20:13:42 +01:00
dependabot[bot]
c82e537dc7 Bump qs from 6.14.1 to 6.14.2 (#353)
Bumps [qs](https://github.com/ljharb/qs) from 6.14.1 to 6.14.2.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-15 01:23:24 +01:00
slawkens
4c3f877091 Update CHANGELOG-2.x.md 2026-02-12 18:21:19 +01:00
slawkens
8b10f85bc1 Start v1.8.9-dev 2026-02-04 18:37:03 +01:00
70 changed files with 919 additions and 1951 deletions

View File

@@ -1,5 +1,18 @@
# Changelog
## [1.8.9 - 06.04.2026]
### Added
* Settings: Possibility to add custom HTML for the head and body tags like Google Analytics code etc. (https://github.com/slawkens/myaac/commit/108e83806df5686a06826931ed5e243c19cbe130)
* Add command: give-admin (https://github.com/slawkens/myaac/commit/9fa9ec746c4b344387a21f21886c2251319806fc)
* Usage: php aac give:admin slawkens@gmail.com
Parameter: account email, name or id
* It's admin for the website, not the GM for the game! For that, go into the admin panel and change the group manually
* Add page load time to an Admin Panel footer (https://github.com/slawkens/myaac/commit/4ae2fdd0dfcd56697612395c14aecc2dfd33b1c3)
### Changed
* Better character name validation, like in the original game website (#356)
* Install: don't suggest deleting of install folder - it's not required (https://github.com/slawkens/myaac/commit/5fcde4708a39255cf68edc8c43f2ac6597e2601d)
## [1.8.8 - 31.01.2026]
### Added
* Change Comment: Add missing hooks - patched from 0.8 (https://github.com/slawkens/myaac/commit/a60a23b84f61d41d1503073b52e01e3120f6d92a)

View File

@@ -1,9 +1,12 @@
## [2.0-dev - x.x.2025]
### Added
* Add an "access" option to Menus (#340)
* Menus: Add an "access" option to Menus (#340)
* Possibility to hide menus for unauthorized users
* Add the possibility to fetch skills in the getTopPlayers function (#347)
* Settings: Add Reset button (https://github.com/slawkens/myaac/commit/7104c2258fd724a55239821b46a616dab845b22a, https://github.com/slawkens/myaac/commit/e274b8350451a20c24e652ea05ed1964ebb86b54)
* New Setting: block create account spam by ip (https://github.com/slawkens/myaac/commit/54265f42e987522803288477952d6e5c4daeeb24)
* Functions: Add the possibility to fetch skills, balance and frags in the getTopPlayers function (#347)
* Plugins: autoload init-priority option (https://github.com/slawkens/myaac/commit/f1aa12840875960849fa0c99a2bbe0ad2949bbec)
### Changed
* Better handling of vocations: (#345)
@@ -11,6 +14,7 @@
* Support for Monk vocation
* Better gallery, loads images from images/gallery folder
* Reworked account action logs to use a single IP column as varchar(45) for both ipv4 and ipv6 (#289)
* Make myaac_config table columns bigger (https://github.com/slawkens/myaac/commit/2c62a97160a3ffe9976ee5bd1d770a0abc576742)
* Admin Panel: save menu collapse state (https://github.com/slawkens/myaac/commit/55da00520df7463a1d1ca41931df1598e9f2ffeb)
### Internal

View File

@@ -7,7 +7,7 @@ Official website: https://my-aac.org
[![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/slawkens/myaac/cypress.yml)](https://github.com/slawkens/myaac/actions)
[![License: GPL-3.0](https://img.shields.io/github/license/slawkens/myaac)](https://opensource.org/licenses/gpl-license)
[![Downloads Count](https://img.shields.io/github/downloads/slawkens/myaac/total)](https://github.com/slawkens/myaac/releases)
[![OpenTibia Discord](https://img.shields.io/discord/288399552581468162)](https://discord.gg/2J39Wus)
[![MyAAC Discord](https://img.shields.io/discord/1468205461319848049)](https://discord.gg/aVagGPJt3g)
[![Closed Issues](https://img.shields.io/github/issues-closed-raw/slawkens/myaac)](https://github.com/slawkens/myaac/issues?q=is%3Aissue+is%3Aclosed)
| Version | Status | Branch | Requirements |

View File

@@ -46,6 +46,15 @@ if (!is_array($settingsFile)) {
return;
}
if (isset($_POST['reset']) && $_POST['reset'] == '1') {
$settings = Settings::getInstance();
$settings->deleteFromDatabase($settingsFile['key']);
$settings->clearCache();
success('Settings for this plugin has been reset.');
}
$settingsKeyName = ($plugin == 'core' ? $plugin : $settingsFile['key']);
$title = ($plugin == 'core' ? 'Settings' : 'Plugin Settings - ' . $settingsFile['name']);
@@ -57,4 +66,5 @@ $twig->display('admin.settings.html.twig', [
'settings' => $settingsFile['settings'],
'script' => $settingsParsed['script'],
'settingsKeyName' => $settingsKeyName,
'pluginName' => $plugin,
]);

View File

@@ -172,7 +172,8 @@
<div class="float-sm-right d-none d-sm-inline">
<span class="p-2 right badge badge-<?php echo((isset($status['online']) and $status['online']) ? 'success' : 'danger'); ?>"><?php echo $config['lua']['serverName'] ?></span>
</div>
<?php echo base64_decode('UG93ZXJlZCBieSA8YSBocmVmPSJodHRwOi8vbXktYWFjLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPk15QUFDLjwvYT4='); ?>
<?= base64_decode('UG93ZXJlZCBieSA8YSBocmVmPSJodHRwOi8vbXktYWFjLm9yZyIgdGFyZ2V0PSJfYmxhbmsiPk15QUFDLjwvYT4='); ?>
<?= 'Load time: ' . round(microtime(true) - START_TIME, 4) . ' seconds.'; ?>
</footer>
<div id="sidebar-overlay"></div>
</div>

View File

@@ -27,7 +27,7 @@ if (version_compare(phpversion(), '8.1', '<')) die('PHP version 8.1 or higher is
const MYAAC = true;
const MYAAC_VERSION = '2.0-dev';
const DATABASE_VERSION = 51;
const DATABASE_VERSION = 52;
const TABLE_PREFIX = 'myaac_';
define('START_TIME', microtime(true));
define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX'));

View File

@@ -19,8 +19,7 @@
"symfony/var-dumper": "^6.4",
"filp/whoops": "^2.15",
"maximebf/debugbar": "1.*",
"guzzlehttp/guzzle": "7.9.3",
"spomky-labs/otphp": "^11.3"
"guzzlehttp/guzzle": "7.9.3"
},
"require-dev": {
"phpstan/phpstan": "^1.10"

713
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,16 +5,9 @@ CREATE TABLE IF NOT EXISTS `myaac_account_actions`
`ip` varchar(45) NOT NULL DEFAULT '',
`date` int NOT NULL DEFAULT 0,
`action` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;
CREATE TABLE IF NOT EXISTS `myaac_account_email_codes`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL,
`code` varchar(6) NOT NULL,
`created_at` int NOT NULL,
PRIMARY KEY (`id`)
PRIMARY KEY (`id`),
INDEX `myaac_account_actions_account_id` (`account_id`),
INDEX `myaac_account_actions_ip` (`ip`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;
CREATE TABLE IF NOT EXISTS `myaac_account_emails_verify`
@@ -52,8 +45,8 @@ CREATE TABLE IF NOT EXISTS `myaac_changelog`
CREATE TABLE IF NOT EXISTS `myaac_config`
(
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
`value` varchar(1000) NOT NULL,
`name` varchar(255) NOT NULL,
`value` varchar(10000) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE (`name`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;

View File

@@ -30,7 +30,7 @@ if(file_exists(CACHE . 'install.txt')) {
$install_status = unserialize(file_get_contents(CACHE . 'install.txt'));
if(!isset($_REQUEST['step'])) {
$step = isset($install_status['step']) ? $install_status['step'] : '';
$step = $install_status['step'] ?? '';
}
}
@@ -53,7 +53,7 @@ if($step == 'finish' && (!isset($config['installed']) || !$config['installed']))
// step verify
$steps = array(1 => 'welcome', 2 => 'license', 3 => 'requirements', 4 => 'config', 5 => 'database', 6 => 'admin', 7 => 'finish');
if(!in_array($step, $steps)) // check if step is valid
if(!in_array($step, $steps)) // check if a step is valid
throw new RuntimeException('ERROR: Unknown step.');
$install_status['step'] = $step;
@@ -61,7 +61,7 @@ $errors = array();
if($step == 'database') {
foreach($_SESSION as $key => $value) {
if(strpos($key, 'var_') === false) {
if(!str_contains($key, 'var_')) {
continue;
}
@@ -182,7 +182,7 @@ $error = false;
clearstatcache();
if(is_writable(CACHE) && (MYAAC_OS != 'WINDOWS' || win_is_writable(CACHE))) {
if(!file_exists(BASE . 'install/ip.txt')) {
$content = warning('AAC installation is disabled. To enable it make file <b>ip.txt</b> in install/ directory and put there your IP.<br/>
$content = warning('AAC installation is disabled. To enable it make a file <b>ip.txt</b> in install/ directory and put there your IP.<br/>
Your IP is:<br /><b>' . get_browser_real_ip() . '</b>', true);
}
else {
@@ -198,7 +198,7 @@ if(is_writable(CACHE) && (MYAAC_OS != 'WINDOWS' || win_is_writable(CACHE))) {
if(!$allow)
{
$content = warning('In file <b>install/ip.txt</b> must be your IP!<br/>
In file is:<br /><b>' . nl2br($file_content) . '</b><br/>
In the file is:<br /><b>' . nl2br($file_content) . '</b><br/>
Your IP is:<br /><b>' . get_browser_real_ip() . '</b>', true);
}
else {

View File

@@ -98,16 +98,6 @@ if(!$db->hasColumn('accounts', 'web_flags')) {
success($locale['step_database_adding_field'] . ' accounts.web_flags...');
}
if(!$db->hasColumn('accounts', '2fa_type')) {
if(query("ALTER TABLE `accounts` ADD `2fa_type` tinyint NOT NULL DEFAULT 0 AFTER `web_flags`;"))
success($locale['step_database_adding_field'] . ' accounts.2fa_type...');
}
if(!$db->hasColumn('accounts', '2fa_secret')) {
if(query("ALTER TABLE `accounts` ADD `2fa_secret` varchar(16) NOT NULL DEFAULT '' AFTER `2fa_type`;"))
success($locale['step_database_adding_field'] . ' accounts.2fa_secret...');
}
if(!$db->hasColumn('accounts', 'email_verified')) {
if(query("ALTER TABLE `accounts` ADD `email_verified` TINYINT(1) NOT NULL DEFAULT 0 AFTER `web_flags`;"))
success($locale['step_database_adding_field'] . ' accounts.email_verified...');

View File

@@ -30,6 +30,8 @@ $up();
DataLoader::setLocale($locale);
DataLoader::load();
clearCache();
// add menus entries
require_once SYSTEM . 'migrations/17.php';
$up();
@@ -67,6 +69,10 @@ if(file_exists(CACHE . 'install.txt')) {
unlink(CACHE . 'install.txt');
}
if(file_exists(BASE . 'install/ip.txt')) {
unlink(BASE . 'install/ip.txt');
}
$locale['step_finish_desc'] = str_replace('$ADMIN_PANEL$', generateLink(str_replace('tools/', '',ADMIN_URL), $locale['step_finish_admin_panel'], true), $locale['step_finish_desc']);
$locale['step_finish_desc'] = str_replace('$HOMEPAGE$', generateLink(str_replace('tools/', '', BASE_URL), $locale['step_finish_homepage'], true), $locale['step_finish_desc']);
$locale['step_finish_desc'] = str_replace('$LINK$', generateLink('https://my-aac.org', 'https://my-aac.org', true), $locale['step_finish_desc']);

100
login.php
View File

@@ -5,7 +5,6 @@ use MyAAC\Models\PlayerOnline;
use MyAAC\Models\Account;
use MyAAC\Models\Player;
use MyAAC\RateLimit;
use MyAAC\TwoFactorAuth\TwoFactorAuth;
require_once 'common.php';
require_once SYSTEM . 'functions.php';
@@ -13,7 +12,7 @@ require_once SYSTEM . 'init.php';
require_once SYSTEM . 'status.php';
# error function
function sendError($message, $code = 3) {
function sendError($message, $code = 3){
$ret = [];
$ret['errorCode'] = $code;
$ret['errorMessage'] = $message;
@@ -94,9 +93,9 @@ switch ($action) {
$creatureBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_creature'))->fetchAll();
$bossBoost = $db->query("SELECT * FROM " . $db->tableName('boosted_boss'))->fetchAll();
die(json_encode([
'boostedcreature' => true,
//'boostedcreature' => true,
'bossraceid' => intval($bossBoost[0]['raceid']),
'creatureraceid' => intval($creatureBoost[0]['raceid']),
'bossraceid' => intval($bossBoost[0]['raceid'])
]));
}
@@ -109,18 +108,17 @@ switch ($action) {
case 'login':
$ip = configLua('ip');
$port = configLua('gameProtocolPort');
$port = $config['lua']['gameProtocolPort'];
// default world info
$world = [
'id' => 0,
'name' => $config['lua']['serverName'],
'externaladdress' => $ip,
'externaladdress' => $config['lua']['ip'],
'externalport' => $port,
'externaladdressprotected' => $ip,
'externaladdressprotected' => $config['lua']['ip'],
'externalportprotected' => $port,
'externaladdressunprotected' => $ip,
'externaladdressunprotected' => $config['lua']['ip'],
'externalportunprotected' => $port,
'previewstate' => 0,
'location' => 'BRA', // BRA, EUR, USA
@@ -135,12 +133,13 @@ switch ($action) {
$inputEmail = $request->email ?? false;
$inputAccountName = $request->accountname ?? false;
$inputToken = $request->token ?? false;
$account = Account::query();
if ($inputEmail) { // login by email
if ($inputEmail != false) { // login by email
$account->where('email', $inputEmail);
}
else if($inputAccountName) { // login by account name
else if($inputAccountName != false) { // login by account name
$account->where('name', $inputAccountName);
}
@@ -152,14 +151,13 @@ switch ($action) {
$limiter->load();
$ban_msg = 'A wrong account, password or secret has been entered ' . setting('core.account_login_attempts_limit') . ' times in a row. You are unable to log into your account for the next ' . setting('core.account_login_ban_time') . ' minutes. Please wait.';
if (!$account) {
$limiter->increment($ip);
if ($limiter->exceeded($ip)) {
sendError($ban_msg);
}
sendError(($inputEmail ? 'Email' : 'Account name') . ' or password is not correct.');
sendError(($inputEmail != false ? 'Email' : 'Account name') . ' or password is not correct.');
}
$current_password = encrypt((USE_ACCOUNT_SALT ? $account->salt : '') . $request->password);
@@ -169,30 +167,32 @@ switch ($action) {
sendError($ban_msg);
}
sendError(($inputEmail ? 'Email' : 'Account name') . ' or password is not correct.');
sendError(($inputEmail != false ? 'Email' : 'Account name') . ' or password is not correct.');
}
$twoFactorAuth = TwoFactorAuth::getInstance($account->id);
$code = '';
if ($twoFactorAuth->isActive()) {
if ($twoFactorAuth->getAuthType() === TwoFactorAuth::TYPE_EMAIL) {
$code = $request->emailcode ?? false;
$accountHasSecret = false;
if (fieldExist('secret', 'accounts')) {
$accountSecret = $account->secret;
if ($accountSecret != null && $accountSecret != '') {
$accountHasSecret = true;
if ($inputToken === false) {
$limiter->increment($ip);
if ($limiter->exceeded($ip)) {
sendError($ban_msg);
}
else if ($twoFactorAuth->getAuthType() === TwoFactorAuth::TYPE_APP) {
$code = $request->token ?? false;
}
}
$error = '';
$errorCode = 6;
if (!$twoFactorAuth->processClientLogin($code, $error, $errorCode)) {
sendError('Submit a valid two-factor authentication token.', 6);
} else {
require_once LIBS . 'rfc6238.php';
if (TokenAuth6238::verify($accountSecret, $inputToken) !== true) {
$limiter->increment($ip);
if ($limiter->exceeded($ip)) {
sendError($ban_msg);
}
sendError($error, $errorCode);
sendError('Two-factor authentication failed, token is wrong.', 6);
}
}
}
}
$limiter->reset($ip);
@@ -220,6 +220,46 @@ switch ($action) {
}
}
/*
* not needed anymore?
if (fieldExist('premdays', 'accounts') && fieldExist('lastday', 'accounts')) {
$save = false;
$timeNow = time();
$premDays = $account->premdays;
$lastDay = $account->lastday;
$lastLogin = $lastDay;
if ($premDays != 0 && $premDays != PHP_INT_MAX) {
if ($lastDay == 0) {
$lastDay = $timeNow;
$save = true;
} else {
$days = (int)(($timeNow - $lastDay) / 86400);
if ($days > 0) {
if ($days >= $premDays) {
$premDays = 0;
$lastDay = 0;
} else {
$premDays -= $days;
$reminder = ($timeNow - $lastDay) % 86400;
$lastDay = $timeNow - $reminder;
}
$save = true;
}
}
} else if ($lastDay != 0) {
$lastDay = 0;
$save = true;
}
if ($save) {
$account->premdays = $premDays;
$account->lastday = $lastDay;
$account->save();
}
}
*/
$worlds = [$world];
$playdata = compact('worlds', 'characters');
@@ -228,7 +268,7 @@ switch ($action) {
if (!fieldExist('istutorial', 'players')) {
$sessionKey .= "\n";
}
$sessionKey .= ($twoFactorAuth->isActive() && strlen($account->{'2fa_secret'}) > 5) ? $account->{'2fa_secret'} : '';
$sessionKey .= ($accountHasSecret && strlen($accountSecret) > 5) ? $inputToken : '';
// this is workaround to distinguish between TFS 1.x and otservbr
// TFS 1.x requires the number in session key

12
package-lock.json generated
View File

@@ -1431,9 +1431,9 @@
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true,
"license": "MIT"
},
@@ -1743,9 +1743,9 @@
}
},
"node_modules/qs": {
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"version": "6.14.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
"integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {

View File

@@ -4,6 +4,7 @@ require __DIR__ . '/system/libs/pot/OTS.php';
$ots = POT::getInstance();
require __DIR__ . '/system/libs/pot/InvitesDriver.php';
require __DIR__ . '/system/libs/rfc6238.php';
require __DIR__ . '/common.php';
const ACTION = '';

View File

@@ -736,11 +736,17 @@ class OTS_Account extends OTS_Row_DAO implements IteratorAggregate, Countable
*/
public function setCustomField($field, $value)
{
if( !isset($this->data['id']) ) {
if( !isset($this->data['id']) )
{
throw new E_OTS_NotLoaded();
}
AccountModel::where('id', $this->data['id'])->update([$field => $value]);
// quotes value for SQL query
if(!( is_int($value) || is_float($value) ))
{
$value = $this->db->quote($value);
}
$this->db->exec('UPDATE ' . $this->db->tableName('accounts') . ' SET ' . $this->db->fieldName($field) . ' = ' . $value . ' WHERE ' . $this->db->fieldName('id') . ' = ' . $this->data['id']);
}
/**

284
system/libs/rfc6238.php Normal file
View File

@@ -0,0 +1,284 @@
<?php
/** https://github.com/Voronenko/PHPOTP/blob/08cda9cb9c30b7242cf0b3a9100a6244a2874927/code/base32static.php
* Encode in Base32 based on RFC 4648.
* Requires 20% more space than base64
* Great for case-insensitive filesystems like Windows and URL's (except for = char which can be excluded using the pad option for urls)
*
* @package default
* @author Bryan Ruiz
**/
class Base32Static {
private static $map = array(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23
'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31
'=' // padding character
);
private static $flippedMap = array(
'A'=>'0', 'B'=>'1', 'C'=>'2', 'D'=>'3', 'E'=>'4', 'F'=>'5', 'G'=>'6', 'H'=>'7',
'I'=>'8', 'J'=>'9', 'K'=>'10', 'L'=>'11', 'M'=>'12', 'N'=>'13', 'O'=>'14', 'P'=>'15',
'Q'=>'16', 'R'=>'17', 'S'=>'18', 'T'=>'19', 'U'=>'20', 'V'=>'21', 'W'=>'22', 'X'=>'23',
'Y'=>'24', 'Z'=>'25', '2'=>'26', '3'=>'27', '4'=>'28', '5'=>'29', '6'=>'30', '7'=>'31'
);
/**
* Use padding false when encoding for urls
*
* @return base32 encoded string
* @author Bryan Ruiz
**/
public static function encode($input, $padding = true) {
if(empty($input)) return "";
$input = str_split($input);
$binaryString = "";
for($i = 0; $i < count($input); $i++) {
$binaryString .= str_pad(base_convert(ord($input[$i]), 10, 2), 8, '0', STR_PAD_LEFT);
}
$fiveBitBinaryArray = str_split($binaryString, 5);
$base32 = "";
$i=0;
while($i < count($fiveBitBinaryArray)) {
$base32 .= self::$map[base_convert(str_pad($fiveBitBinaryArray[$i], 5,'0'), 2, 10)];
$i++;
}
if($padding && ($x = strlen($binaryString) % 40) != 0) {
if($x == 8) $base32 .= str_repeat(self::$map[32], 6);
else if($x == 16) $base32 .= str_repeat(self::$map[32], 4);
else if($x == 24) $base32 .= str_repeat(self::$map[32], 3);
else if($x == 32) $base32 .= self::$map[32];
}
return $base32;
}
public static function decode($input) {
if(empty($input)) return;
$paddingCharCount = substr_count($input, self::$map[32]);
$allowedValues = array(6,4,3,1,0);
if(!in_array($paddingCharCount, $allowedValues)) return false;
for($i=0; $i<4; $i++){
if($paddingCharCount == $allowedValues[$i] &&
substr($input, -($allowedValues[$i])) != str_repeat(self::$map[32], $allowedValues[$i])) return false;
}
$input = str_replace('=','', $input);
$input = str_split($input);
$binaryString = "";
for($i=0; $i < count($input); $i = $i+8) {
$x = "";
if(!in_array($input[$i], self::$map)) return false;
for($j=0; $j < 8; $j++) {
$x .= str_pad(base_convert(@self::$flippedMap[@$input[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
}
$eightBits = str_split($x, 8);
for($z = 0; $z < count($eightBits); $z++) {
$binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:"";
}
}
return $binaryString;
}
}
// http://www.faqs.org/rfcs/rfc6238.html
// https://github.com/Voronenko/PHPOTP/blob/08cda9cb9c30b7242cf0b3a9100a6244a2874927/code/rfc6238.php
// Local changes: http -> https, consistent indentation, 200x200 -> 300x300 QR image size, PHP end tag
class TokenAuth6238 {
/**
* verify
*
* @param string $secretkey Secret clue (base 32).
* @return bool True if success, false if failure
*/
public static function verify($secretkey, $code, $rangein30s = 3) {
$key = base32static::decode($secretkey);
$unixtimestamp = time()/30;
for($i=-($rangein30s); $i<=$rangein30s; $i++) {
$checktime = (int)($unixtimestamp+$i);
$thiskey = self::oath_hotp($key, $checktime);
if ((int)$code == self::oath_truncate($thiskey,6)) {
return true;
}
}
return false;
}
public static function getTokenCode($secretkey,$rangein30s = 3) {
$result = "";
$key = base32static::decode($secretkey);
$unixtimestamp = time()/30;
for($i=-($rangein30s); $i<=$rangein30s; $i++) {
$checktime = (int)($unixtimestamp+$i);
$thiskey = self::oath_hotp($key, $checktime);
$result = $result." # ".self::oath_truncate($thiskey,6);
}
return $result;
}
public static function getTokenCodeDebug($secretkey,$rangein30s = 3) {
$result = "";
print "<br/>SecretKey: $secretkey <br/>";
$key = base32static::decode($secretkey);
print "Key(base 32 decode): $key <br/>";
$unixtimestamp = time()/30;
print "UnixTimeStamp (time()/30): $unixtimestamp <br/>";
for($i=-($rangein30s); $i<=$rangein30s; $i++) {
$checktime = (int)($unixtimestamp+$i);
print "Calculating oath_hotp from (int)(unixtimestamp +- 30sec offset): $checktime basing on secret key<br/>";
$thiskey = self::oath_hotp($key, $checktime, true);
print "======================================================<br/>";
print "CheckTime: $checktime oath_hotp:".$thiskey."<br/>";
$result = $result." # ".self::oath_truncate($thiskey,6,true);
}
return $result;
}
public static function getBarCodeUrl($username, $domain, $secretkey, $issuer) {
$url = "https://chart.apis.google.com/chart";
$url = $url."?chs=300x300&chld=M|0&cht=qr&chl=otpauth://totp/";
$url = $url.$username . "@" . $domain . "%3Fsecret%3D" . $secretkey . '%26issuer%3D' . rawurlencode($issuer);
return $url;
}
public static function generateRandomClue($length = 16) {
$b32 = "234567QWERTYUIOPASDFGHJKLZXCVBNM";
$s = "";
for ($i = 0; $i < $length; $i++)
$s .= $b32[rand(0,31)];
return $s;
}
private static function hotp_tobytestream($key) {
$result = array();
$last = strlen($key);
for ($i = 0; $i < $last; $i = $i + 2) {
$x = $key[$i] + $key[$i + 1];
$x = strtoupper($x);
$x = hexdec($x);
$result = $result.chr($x);
}
return $result;
}
private static function oath_hotp ($key, $counter, $debug=false) {
$result = "";
$orgcounter = $counter;
$cur_counter = array(0,0,0,0,0,0,0,0);
if ($debug) {
print "Packing counter $counter (".dechex($counter).")into binary string - pay attention to hex representation of key and binary representation<br/>";
}
for($i=7;$i>=0;$i--) { // C for unsigned char, * for repeating to the end of the input data
$cur_counter[$i] = pack ('C*', $counter);
if ($debug) {
print $cur_counter[$i]."(".dechex(ord($cur_counter[$i])).")"." from $counter <br/>";
}
$counter = $counter >> 8;
}
if ($debug) {
foreach ($cur_counter as $char) {
print ord($char) . " ";
}
print "<br/>";
}
$binary = implode($cur_counter);
// Pad to 8 characters
str_pad($binary, 8, chr(0), STR_PAD_LEFT);
if ($debug) {
print "Prior to HMAC calculation pad with zero on the left until 8 characters.<br/>";
print "Calculate sha1 HMAC(Hash-based Message Authentication Code http://en.wikipedia.org/wiki/HMAC).<br/>";
print "hash_hmac ('sha1', $binary, $key)<br/>";
}
$result = hash_hmac ('sha1', $binary, $key);
if ($debug) {
print "Result: $result <br/>";
}
return $result;
}
private static function oath_truncate($hash, $length = 6, $debug=false) {
$result="";
// Convert to dec
if($debug) {
print "converting hex hash into characters<br/>";
}
$hashcharacters = str_split($hash,2);
if($debug) {
print_r($hashcharacters);
print "<br/>and convert to decimals:<br/>";
}
for ($j=0; $j<count($hashcharacters); $j++) {
$hmac_result[]=hexdec($hashcharacters[$j]);
}
if($debug) {
print_r($hmac_result);
}
// http://php.net/manual/ru/function.hash-hmac.php
// adopted from brent at thebrent dot net 21-May-2009 08:17 comment
$offset = $hmac_result[19] & 0xf;
if($debug) {
print "Calculating offset as 19th element of hmac:".$hmac_result[19]."<br/>";
print "offset:".$offset;
}
$result = (
(($hmac_result[$offset+0] & 0x7f) << 24 ) |
(($hmac_result[$offset+1] & 0xff) << 16 ) |
(($hmac_result[$offset+2] & 0xff) << 8 ) |
($hmac_result[$offset+3] & 0xff)
) % pow(10,$length);
return $result;
}
}

View File

@@ -20,7 +20,7 @@ $locale['not_loaded'] = 'Nicht geladen';
$locale['loading_spinner'] = 'Bitte warten, installieren...';
$locale['importing_spinner'] = 'Bitte warte, Daten werden importiert...';
$locale['please_fill_all'] = 'Bitte füllen Sie alle Felder aus!';
$locale['already_installed'] = 'MyAAC wurde bereits installiert. Bitte löschen <b>install/</b> Verzeichnis. Wenn Sie MyAAC neu installieren möchten, löschen Sie die Datei <strong>config.local.php</strong> aus dem Hauptverzeichnis und aktualisieren Sie die Seite.';
$locale['already_installed'] = 'MyAAC wurde bereits installiert. Wenn Sie MyAAC neu installieren möchten, löschen Sie die Datei <strong>config.local.php</strong> aus dem Hauptverzeichnis und aktualisieren Sie die Seite.';
// welcome
$locale['step_welcome'] = 'Willkommen';

View File

@@ -20,7 +20,7 @@ $locale['not_loaded'] = 'Not loaded';
$locale['loading_spinner'] = 'Please wait, installing...';
$locale['importing_spinner'] = 'Please wait, importing data...';
$locale['please_fill_all'] = 'Please fill all inputs!';
$locale['already_installed'] = 'MyAAC has been already installed. Please delete <b>install/</b> directory. If you want to reinstall MyAAC - please delete <strong>config.local.php</strong> file from the main directory and refresh the page.';
$locale['already_installed'] = 'MyAAC has been already installed. If you want to reinstall MyAAC - please delete <strong>config.local.php</strong> file from the main directory and refresh the page.';
// welcome
$locale['step_welcome'] = 'Welcome';

View File

@@ -20,7 +20,7 @@ $locale['not_loaded'] = 'Nie załadowane';
$locale['loading_spinner'] = 'Proszę czekać, trwa instalacja...';
$locale['importing_spinner'] = 'Proszę czekać, trwa importowanie danych...';
$locale['please_fill_all'] = 'Proszę wypełnić wszystkie pola!';
$locale['already_installed'] = 'MyAAC został już zainstalowany. Proszę usunąć katalog <b>install/</b>. Jeśli chcesz zainstalować MyAAC od nowa - proszę usuń plik <strong>config.local.php</strong> z katalogu głównego i odśwież stronę.';
$locale['already_installed'] = 'MyAAC został już zainstalowany. Jeśli chcesz zainstalować MyAAC od nowa - proszę usuń plik <strong>config.local.php</strong> z katalogu głównego i odśwież stronę.';
// welcome
$locale['step_welcome'] = 'Witamy';

View File

@@ -20,7 +20,7 @@ $locale['not_loaded'] = 'Não carregado';
$locale['loading_spinner'] = 'Por favor aguarde, instalando...';
$locale['importing_spinner'] = 'Por favor, aguarde, importando dados...';
$locale['please_fill_all'] = 'Por favor, preencha todas as entradas!';
$locale['already_installed'] = 'MyAAC já foi instalado. Por favor, apague o diretório <b> install/ <b/>. Se você quiser reinstalar o MyAAC - exclua o arquivo <strong> config.local.php </strong> do diretório principal e atualize a página.';
$locale['already_installed'] = 'MyAAC já foi instalado. Se você quiser reinstalar o MyAAC - exclua o arquivo <strong> config.local.php </strong> do diretório principal e atualize a página.';
// welcome
$locale['step_welcome'] = 'Bem vindo';

View File

@@ -18,7 +18,7 @@ $locale['loaded'] = 'Laddad';
$locale['not_loaded'] = 'Inte Laddad';
$locale['please_fill_all'] = 'Vänligen fyll i allt!';
$locale['already_installed'] = 'MyAAC är redan installerat. Vänligen ta bort <b>install/<b/> mappen. Om du vill installera MyAAC igen - ta bort filen <strong>config.local.php</strong> från huvudkatalogen och uppdatera sidan.';
$locale['already_installed'] = 'MyAAC är redan installerat. Om du vill installera MyAAC igen - ta bort filen <strong>config.local.php</strong> från huvudkatalogen och uppdatera sidan.';
// welcome
$locale['step_welcome'] = 'Välkommen';

View File

@@ -1,8 +0,0 @@
CREATE TABLE `myaac_account_email_codes`
(
`id` int(11) NOT NULL AUTO_INCREMENT,
`account_id` int NOT NULL,
`code` varchar(6) NOT NULL,
`created_at` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;

View File

@@ -1,36 +1,10 @@
<?php
// 2fa
// add the myaac_account_email_codes
/**
* @var OTS_DB_MySQL $db
*/
$up = function () use ($db) {
if (!$db->hasColumn('accounts', '2fa_type')) {
$db->addColumn('accounts', '2fa_type', "tinyint NOT NULL DEFAULT 0 AFTER `web_flags`");
}
if (!$db->hasColumn('accounts', '2fa_secret')) {
$db->addColumn('accounts', '2fa_secret', "varchar(16) NOT NULL DEFAULT '' AFTER `2fa_type`");
}
// add myaac_account_email_codes table
if (!$db->hasTable(TABLE_PREFIX . 'account_email_codes')) {
$db->exec(file_get_contents(__DIR__ . '/51-account_email_codes.sql'));
}
$db->modifyColumn(TABLE_PREFIX . 'config', 'name', "varchar(255) NOT NULL");
$db->modifyColumn(TABLE_PREFIX . 'config', 'value', "varchar(10000) NOT NULL");
};
$down = function () use ($db) {
if ($db->hasColumn('accounts', '2fa_type')) {
$db->dropColumn('accounts', '2fa_type');
}
if ($db->hasColumn('accounts', '2fa_secret')) {
$db->dropColumn('accounts', '2fa_secret');
}
if ($db->hasTable(TABLE_PREFIX . 'account_email_codes')) {
$db->dropTable(TABLE_PREFIX . 'account_email_codes');
}
$down = function () {
// nothing to do, to not lose data
};

13
system/migrations/52.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
/**
* 2026-04-12
* Add indexes to myaac_account_actions table
*/
$up = function () use ($db) {
$db->query("CREATE INDEX `myaac_account_actions_account_id` ON `myaac_account_actions` (`account_id`);");
$db->query("CREATE INDEX `myaac_account_actions_ip` ON `myaac_account_actions` (`ip`);");
};
$down = function () {
// nothing to do, to not lose data
};

View File

@@ -1,26 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
require __DIR__ . '/../base.php';
if (!isRequestMethod('post')) {
error('This page cannot be accessed directly.');
return;
}
if (!$account_logged->isLoaded()) {
error('Account not found!');
return;
}
if (!$twoFactorAuth->isActive($twoFactorAuth::TYPE_APP)) {
error("Your account does not have Two Factor App Authentication enabled.");
return;
}
$twoFactorAuth->disable();
$twig->display('success.html.twig', [
'title' => 'Disabled',
'description' => 'Two Factor App Authentication has been disabled.'
]);

View File

@@ -1,105 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
use MyAAC\TwoFactorAuth\TwoFactorAuth;
require __DIR__ . '/../base.php';
if ($twoFactorAuth->isActive()) {
$errors[] = 'Two-factor authentication is already enabled on your account.';
$twig->display('error_box.html.twig', ['errors' => $errors]);
return;
}
$explodeRecoveryKey = explode('-', $account_logged->getCustomField('key'));
$newRecoveryKeyFormat = (count($explodeRecoveryKey) == 4);
if (ACTION == 'request') {
if ($newRecoveryKeyFormat) {
$key = $_POST['key1'] . '-' . $_POST['key2'] . '-' . $_POST['key3'] . '-' . $_POST['key4'];
}
else {
$key = $_POST['key'];
}
$accountKey = $account_logged->getCustomField('key');
if (!empty($key) && $key == $accountKey) {
$secret = getSession('2fa_secret');
if ($secret === null) {
$secret = generateRandom2faSecret();
setSession('2fa_secret', $secret);
}
$twoFactorAuth->appDisplayEnable($secret);
return;
}
else {
if (empty($key)) {
$errors[] = 'Please enter the recovery key!';
}
else {
$errors[] = 'Invalid recovery key!';
}
}
}
if (ACTION == 'link') {
$secret = getSession('2fa_secret');
if ($secret === null) {
$twig->display('error_box.html.twig', ['errors' => ['Secret not set. Go back and try again.']]);
return;
}
$authCode = $_POST['auth-code'] ?? '';
if (!empty($authCode)) {
$otp = $twoFactorAuth->appInitTOTP($secret);
if (!$otp->verify($authCode)) {
$errors = ['Token is invalid!'];
$twig->display('error_box.html.twig', ['errors' => $errors]);
$twoFactorAuth->appDisplayEnable($secret, $otp, $errors);
return;
}
if ($db->hasColumn('accounts', 'secret')) {
$account_logged->setCustomField('secret', $secret);
}
$account_logged->setCustomField('2fa_secret', $secret);
$twoFactorAuth->enable(TwoFactorAuth::TYPE_APP);
$twig->display('success.html.twig',
[
'title' => 'Authenticator App Connected',
'description' => 'You successfully connected your Tibia account to an authenticator app.'
]
);
return;
}
else {
$errors = ['You have to enter the code generated by the authenticator!'];
$twig->display('error_box.html.twig', ['errors' => $errors]);
$twoFactorAuth->appDisplayEnable($secret, null, $errors);
return;
}
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', ['errors' => $errors]);
}
$twig->display('account/2fa/app/enable.warning.html.twig',
[
'newRecoveryKeyFormat' => $newRecoveryKeyFormat,
'errors' => $errors,
]
);

View File

@@ -1,41 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
use MyAAC\TwoFactorAuth\TwoFactorAuth;
csrfProtect();
$title = 'Two Factor Authentication';
/**
* @var OTS_Account $account_logged
*/
$code = $_REQUEST['auth-code'] ?? '';
if (!$account_logged->isLoaded()) {
$current_session = getSession('account');
if($current_session) {
$account_logged = new OTS_Account();
$account_logged->load($current_session);
}
}
$twoFactorAuth = TwoFactorAuth::getInstance($account_logged);
$twig->addGlobal('account_logged', $account_logged);
/**
* Took from ZnoteAAC
* @author Znote
*/
function generateRandom2faSecret($length = 16): string
{
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}

View File

@@ -1,34 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
require __DIR__ . '/../base.php';
if ((!setting('core.mail_enabled'))) {
$twig->display('error_box.html.twig', ['errors' => ['Account Two-Factor E-Mail Authentication disabled.']]);
return;
}
if (!isRequestMethod('post')) {
error('This page cannot be accessed directly.');
return;
}
if (!$account_logged->isLoaded()) {
error('Account not found!');
return;
}
if (!$twoFactorAuth->isActive($twoFactorAuth::TYPE_EMAIL)) {
error("Your account does not have Two Factor E-Mail Authentication enabled.");
return;
}
$twoFactorAuth->disable();
$twoFactorAuth->deleteOldCodes();
$twig->display('success.html.twig',
[
'title' => 'Email Code Authentication Disabled',
'description' => 'You have successfully <strong>disabled</strong> the <b>Email Code Authentication</b> for your account.'
]
);

View File

@@ -1,51 +0,0 @@
<?php
use MyAAC\TwoFactorAuth\TwoFactorAuth;
defined('MYAAC') or die('Direct access not allowed!');
require __DIR__ . '/../base.php';
if ((!setting('core.mail_enabled'))) {
$twig->display('error_box.html.twig', ['errors' => ['Account Two-Factor E-Mail Authentication disabled.']]);
return;
}
if ($twoFactorAuth->isActive()) {
$errors[] = 'Two-factor authentication is already enabled on your account.';
$twig->display('error_box.html.twig', ['errors' => $errors]);
return;
}
if (!$twoFactorAuth->hasRecentEmailCode(15 * 60)) {
$twoFactorAuth->resendEmailCode();
}
if (isset($_POST['save'])) {
if (!empty($code)) {
$twoFactorAuth->setAuthGateway(TwoFactorAuth::TYPE_EMAIL);
if ($twoFactorAuth->getAuthGateway()->verifyCode($code)) {
$serverName = configLua('serverName');
$twoFactorAuth->enable(TwoFactorAuth::TYPE_EMAIL);
$twoFactorAuth->deleteOldCodes();
$twig->display('success.html.twig', [
'title' => 'Email Code Authentication Activated',
'description' => sprintf('You have successfully activated <b>email code authentication</b> for your account. This means an <b>email code</b> will be sent to the email address assigned to your account whenever you try to log in to the %s client or the %s website. In order to log in, you will need to enter the <b>most recent email code</b> you have received.', $serverName, $serverName)
]);
return;
}
else {
$errors[] = 'Invalid email code!';
}
}
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', ['errors' => $errors]);
}
$twig->display('account/2fa/email/enable.html.twig', ['wrongCode' => count($errors) > 0]);

View File

@@ -1,32 +0,0 @@
<?php
defined('MYAAC') or die('Direct access not allowed!');
require __DIR__ . '/../base.php';
if ((!setting('core.mail_enabled'))) {
$twig->display('error_box.html.twig', ['errors' => ['Account Two-Factor E-Mail Authentication disabled.']]);
return;
}
if (!$account_logged->isLoaded()) {
error('Account not found!');
return;
}
if ($twoFactorAuth->isActive($twoFactorAuth::TYPE_APP)) {
error('You have to disable the app auth first!');
return;
}
if ($twoFactorAuth->hasRecentEmailCode(30 * 60)) {
$errors = ['Sorry, one email per 30 minutes'];
}
else {
$twoFactorAuth->resendEmailCode();
}
if (!empty($errors)) {
$twig->display('error_box.html.twig', ['errors' => $errors]);
}
$twig->display('account/2fa/email/enable.html.twig');

View File

@@ -17,10 +17,6 @@ if(!$logged)
if(!empty($errors))
$twig->display('error_box.html.twig', array('errors' => $errors));
if (defined('HIDE_LOGIN_BOX') && HIDE_LOGIN_BOX) {
return;
}
$twig->display('account.login.html.twig', array(
'redirect' => $_REQUEST['redirect'] ?? null,
'account' => USE_ACCOUNT_NAME ? 'Name' : 'Number',
@@ -34,11 +30,3 @@ if(!$logged)
else {
$show_form = true;
}
function generateRecoveryKey(): string
{
return generateRandomString(5, false, true, true) . '-' .
generateRandomString(5, false, true, true) . '-' .
generateRandomString(5, false, true, true) . '-' .
generateRandomString(5, false, true, true);
}

View File

@@ -171,7 +171,7 @@ if($save)
}
if(setting('core.account_create_character_create')) {
$character_name = isset($_POST['name']) ? stripslashes(ucwords(strtolower($_POST['name']))) : null;
$character_name = isset($_POST['name']) ? trim(stripslashes($_POST['name'])) : null;
$character_sex = isset($_POST['sex']) ? (int)$_POST['sex'] : null;
$character_vocation = isset($_POST['vocation']) ? (int)$_POST['vocation'] : null;
$character_town = isset($_POST['town']) ? (int)$_POST['town'] : null;

View File

@@ -10,7 +10,6 @@
*/
use MyAAC\RateLimit;
use MyAAC\TwoFactorAuth\TwoFactorAuth;
defined('MYAAC') or die('Direct access not allowed!');
@@ -53,18 +52,8 @@ if(!empty($login_account) && !empty($login_password))
$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.<br/>' .
'You can resend the Email here: <a href="' . $link . '">' . $link . '</a>';
} else {
setSession('account', $account_logged->getId());
if (!$hooks->trigger(HOOK_ACCOUNT_LOGIN_PRE)) {
return;
}
$twoFactorAuth = TwoFactorAuth::getInstance($account_logged);
if (!$twoFactorAuth->process($login_account, $login_password, $remember_me, $_POST['auth-code'] ?? '')) {
return;
}
session_regenerate_id();
setSession('account', $account_logged->getId());
setSession('password', encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password));
if($remember_me) {
setSession('remember_me', true);

View File

@@ -8,9 +8,6 @@
* @copyright 2019 MyAAC
* @link https://my-aac.org
*/
use MyAAC\TwoFactorAuth\TwoFactorAuth;
defined('MYAAC') or die('Direct access not allowed!');
$title = 'Account Management';
@@ -119,8 +116,6 @@ $twig->display('account.management.html.twig', array(
'account_registered' => $account_registered,
'account_rlname' => $account_rlname,
'account_location' => $account_location,
'twoFactorViews' => TwoFactorAuth::getInstance($account_logged)->getAccountManageViews(),
'actions' => $actions,
'players' => $account_players,
'players' => $account_players
));

View File

@@ -37,7 +37,7 @@ else
if($points >= setting('core.account_generate_new_reckey_price'))
{
$show_form = false;
$new_rec_key = generateRecoveryKey();
$new_rec_key = generateRandomString(10, false, true, true);
$mailBody = $twig->render('mail.account.register.html.twig', array(
'recovery_key' => $new_rec_key

View File

@@ -27,7 +27,7 @@ if(isset($_POST['registeraccountsave']) && $_POST['registeraccountsave'] == "1")
if($reg_password == $account_logged->getPassword()) {
if(empty($old_key)) {
$show_form = false;
$new_rec_key = generateRecoveryKey();
$new_rec_key = generateRandomString(10, false, true, true);
$account_logged->setCustomField("key", $new_rec_key);
$account_logged->logAction('Generated recovery key.');

View File

@@ -39,7 +39,7 @@ class GiveAdminCommand extends Command
}
if (!$account->isLoaded()) {
$io->error('Cannot find account mit supplied parameter: ' . $accountParam);
$io->error('Cannot find account with supplied parameter: ' . $accountParam);
return self::FAILURE;
}

View File

@@ -14,6 +14,26 @@ class Hooks
self::$_hooks[$hook->type()][] = $hook;
}
public function unregister($name, $type, $file): void
{
if (is_string($type)) {
$type = constant($type);
}
if(!isset(self::$_hooks[$type])) {
return;
}
foreach(self::$_hooks[$type] as $id => $hook) {
if($name == $hook->name()
&& $type == $hook->type()
&& $file == $hook->file()
) {
unset(self::$_hooks[$type][$id]);
}
}
}
public function trigger($type, $params = []): bool
{
$ret = true;

View File

@@ -18,6 +18,15 @@ class Account extends Model {
public $timestamps = false;
protected $fillable = [
'name', 'number', 'email', 'password',
'key', 'created', 'rlname', 'location', 'country',
'web_lastlogin', 'web_flags',
'email_new', 'email_new_time', 'email_code',
'premium_points', 'coins', 'coins_transferable',
'premium_ends_at', 'premend', 'lastday', 'premdays',
];
protected $casts = [
'lastday' => 'integer',
'premdays' => 'integer',

View File

@@ -1,14 +0,0 @@
<?php
namespace MyAAC\Models;
use Illuminate\Database\Eloquent\Model;
class AccountEMailCode extends Model {
protected $table = TABLE_PREFIX . 'account_email_codes';
public $timestamps = false;
protected $fillable = ['account_id', 'code', 'created_at'];
}

View File

@@ -1,15 +0,0 @@
<?php
namespace MyAAC\Models;
use Illuminate\Database\Eloquent\Model;
class BugTracker extends Model {
protected $table = TABLE_PREFIX . 'bugtracker';
public $timestamps = false;
protected $fillable = ['account', 'type', 'status', 'text', 'id', 'subject', 'reply', 'who', 'uid', 'tag'];
}

View File

@@ -7,9 +7,11 @@ use MyAAC\Cache\Cache;
use MyAAC\Models\Menu;
class Plugins {
private static $warnings = [];
private static $error = null;
private static $plugin_json = [];
private static array $warnings = [];
private static string $error = '';
private static array $plugin_json = [];
const DEFAULT_PRIORITY = 1000;
public static function getInits()
{
@@ -20,13 +22,31 @@ class Plugins {
continue;
}
$initPriority = self::DEFAULT_PRIORITY;
if (isset($plugin['autoload']['init-priority'])) {
$initPriority = (int) $plugin['autoload']['init-priority'];
}
$pluginInits = glob(PLUGINS . $plugin['filename'] . '/init.php');
foreach ($pluginInits as $path) {
$inits[] = $path;
$inits[] = [
'file' => $path,
'priority' => $initPriority
];
}
}
return $inits;
usort($inits, function ($a, $b)
{
return $a['priority'] <=> $b['priority'];
});
$ret = [];
foreach ($inits as $init) {
$ret[] = $init['file'];
}
return $ret;
});
}
@@ -39,7 +59,7 @@ class Plugins {
continue;
}
$adminPagesDefaultPriority = 1000;
$adminPagesDefaultPriority = self::DEFAULT_PRIORITY;
if (isset($plugin['admin-pages-default-priority'])) {
$adminPagesDefaultPriority = $plugin['admin-pages-default-priority'];
}
@@ -117,7 +137,7 @@ class Plugins {
$routes = [];
foreach(self::getAllPluginsJson() as $plugin) {
$routesDefaultPriority = 1000;
$routesDefaultPriority = self::DEFAULT_PRIORITY;
if (isset($plugin['routes-default-priority'])) {
$routesDefaultPriority = $plugin['routes-default-priority'];
}
@@ -165,7 +185,7 @@ class Plugins {
}
}
$pagesDefaultPriority = 1000;
$pagesDefaultPriority = self::DEFAULT_PRIORITY;
if (isset($plugin['pages-default-priority'])) {
$pagesDefaultPriority = $plugin['pages-default-priority'];
}
@@ -318,7 +338,7 @@ class Plugins {
foreach(self::getAllPluginsJson() as $plugin) {
if (isset($plugin['hooks'])) {
foreach ($plugin['hooks'] as $_name => $info) {
$priority = 1000;
$priority = self::DEFAULT_PRIORITY;
if (str_contains($info['type'], 'HOOK_')) {
$info['type'] = str_replace('HOOK_', '', $info['type']);
@@ -432,7 +452,7 @@ class Plugins {
return $plugins;
}
public static function getPluginSettings($filename)
public static function getPluginSettings($filename): mixed
{
$plugin_json = self::getPluginJson($filename);
if (!$plugin_json) {
@@ -868,6 +888,11 @@ class Plugins {
}
}
global $hooks;
foreach($plugin_info['hooks'] ?? [] as $name => $info) {
$hooks->unregister($name, $info['type'], $info['file']);
}
clearCache();
return true;
}
@@ -892,15 +917,15 @@ class Plugins {
return Semver::satisfies($plugin_info['version'], $version);
}
public static function getWarnings() {
public static function getWarnings(): array {
return self::$warnings;
}
public static function clearWarnings() {
public static function clearWarnings(): void {
self::$warnings = [];
}
public static function getError() {
public static function getError(): string {
return self::$error;
}
@@ -911,7 +936,7 @@ class Plugins {
* @param string $templateName
* @param array $menus
*/
public static function installMenus($templateName, $menus, $clearOld = false)
public static function installMenus($templateName, $menus, $clearOld = false): void
{
global $db;
@@ -962,7 +987,7 @@ class Plugins {
}
}
private static function getAutoLoadOption(array $plugin, string $optionName, bool $default = true)
private static function getAutoLoadOption(array $plugin, string $optionName, bool $default = true): bool
{
if (isset($plugin['autoload'])) {
$autoload = $plugin['autoload'];
@@ -971,7 +996,7 @@ class Plugins {
return getBoolean($autoload[$optionName]);
}
}
else if (is_bool($autoload)) {
elseif (is_bool($autoload)) {
return $autoload;
}
}

View File

@@ -367,6 +367,7 @@ class Settings implements \ArrayAccess
</div>
<div class="box-footer">
<button name="save" type="submit" class="btn btn-primary">Save</button>
<button form="reset-settings-form" name="reset" type="submit" class="btn btn-warning position-absolute" style="right: 0; bottom: 0" onclick="return confirm('Are you sure? This will clear all settings for this plugin!')">Reset</button>
</div>
<?php

View File

@@ -1,19 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth\Gateway;
use MyAAC\TwoFactorAuth\Interface\AuthGatewayInterface;
use OTPHP\TOTP;
class AppAuthGateway extends BaseAuthGateway implements AuthGatewayInterface
{
public function verifyCode(string $code): bool
{
$otp = TOTP::createFromSecret($this->account->getCustomField('secret'));
$otp->setLabel($this->account->getEmail());
$otp->setIssuer(configLua('serverName'));
return $otp->verify($code);
}
}

View File

@@ -1,12 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth\Gateway;
class BaseAuthGateway
{
protected \OTS_Account $account;
public function __construct(\OTS_Account $account) {
$this->account = $account;
}
}

View File

@@ -1,16 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth\Gateway;
use MyAAC\Models\AccountEMailCode;
use MyAAC\TwoFactorAuth\Interface\AuthGatewayInterface;
use MyAAC\TwoFactorAuth\TwoFactorAuth;
class EmailAuthGateway extends BaseAuthGateway implements AuthGatewayInterface
{
public function verifyCode(string $code): bool
{
return AccountEMailCode::where('account_id', '=', $this->account->getId())->where('code', $code)->where('created_at', '>', time() - TwoFactorAuth::EMAIL_CODE_VALID_UNTIL)->first() !== null;
}
}

View File

@@ -1,9 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth\Interface;
interface AuthGatewayInterface
{
public function __construct(\OTS_Account $account);
public function verifyCode(string $code): bool;
}

View File

@@ -1,270 +0,0 @@
<?php
namespace MyAAC\TwoFactorAuth;
use MyAAC\Models\AccountEMailCode;
use MyAAC\TwoFactorAuth\Gateway\AppAuthGateway;
use MyAAC\TwoFactorAuth\Gateway\EmailAuthGateway;
use OTPHP\TOTP;
class TwoFactorAuth
{
const TYPE_NONE = 0;
const TYPE_EMAIL = 1;
const TYPE_APP = 2;
// maybe later
//const TYPE_SMS = 3;
const EMAIL_CODE_VALID_UNTIL = 24 * 60 * 60;
private static self $instance;
private \OTS_Account $account;
private int $authType;
private EmailAuthGateway|AppAuthGateway $authGateway;
public function __construct(\OTS_Account|int $account) {
if (is_int($account)) {
$this->account = new \OTS_Account();
$this->account->load($account);
}
else {
$this->account = $account;
}
$this->authType = (int)$this->account->getCustomField('2fa_type');
$this->setAuthGateway($this->authType);
}
public static function getInstance($account = null): self
{
if (!isset(self::$instance)) {
self::$instance = new self($account);
}
return self::$instance;
}
public function process($login_account, $login_password, $remember_me, $code): bool
{
global $twig;
if (!$this->isActive()) {
return true;
}
$view = 'app';
if ($this->authType == self::TYPE_EMAIL) {
$view = 'email';#
}
if (empty($code)) {
if ($this->authType == self::TYPE_EMAIL) {
if (!$this->hasRecentEmailCode(15 * 60)) {
$this->resendEmailCode();
}
}
define('HIDE_LOGIN_BOX', true);
$twig->display("account/2fa/$view/login.html.twig", [
'account_login' => $login_account,
'password_login' => $login_password,
'remember_me' => $remember_me,
]);
return false;
}
if ($this->getAuthGateway()->verifyCode($code)) {
if ($this->authType === self::TYPE_EMAIL) {
$this->deleteOldCodes();
}
return true;
}
if (setting('core.mail_enabled')) {
$mailBody = $twig->render('mail.account.2fa.email-code.wrong-attempt.html.twig');
if (!_mail($this->account->getEMail(), configLua('serverName') . ' - Failed Two-Factor Authentication Attempt', $mailBody)) {
error('An error occurred while sending email. For Admin: More info can be found in system/logs/mailer-error.log');
}
}
define('HIDE_LOGIN_BOX', true);
if ($this->authType == self::TYPE_APP) {
$errors[] = 'The token is invalid!';
}
else {
$errors[] = 'Invalid E-Mail code!';
}
$twig->display('error_box.html.twig', ['errors' => $errors]);
$twig->display("account/2fa/$view/login.html.twig",
[
'account_login' => $login_account,
'password_login' => $login_password,
'remember_me' => $remember_me,
'wrongCode' => true,
]);
return false;
}
public function processClientLogin($code, string &$error, &$errorCode): bool
{
if (!$this->isActive()) {
return true;
}
if ($this->authType == self::TYPE_EMAIL) {
$errorCode = 8;
}
if ($code === false) {
$error = 'Submit a valid two-factor authentication token.';
if ($this->authType == self::TYPE_EMAIL) {
if (!$this->hasRecentEmailCode(15 * 60)) {
$this->resendEmailCode();
}
}
return false;
}
if (!$this->getAuthGateway()->verifyCode($code)) {
$error = 'Two-factor authentication failed, token is wrong.';
return false;
}
if ($this->authType === self::TYPE_EMAIL) {
$this->deleteOldCodes();
}
return true;
}
public function setAuthGateway(int $authType): void
{
if ($authType === self::TYPE_EMAIL) {
$this->authGateway = new EmailAuthGateway($this->account);
}
else if ($authType === self::TYPE_APP) {
$this->authGateway = new AppAuthGateway($this->account);
}
}
public function getAccountManageViews(): array
{
if ($this->authType == self::TYPE_EMAIL) {
$twoFactorView = 'account/2fa/main.protected.html.twig';
$twoFactorView2 = 'account/2fa/email/manage.connected.html.twig';
}
elseif ($this->authType == self::TYPE_APP) {
$twoFactorView = 'account/2fa/app/manage.connected.html.twig';
$twoFactorView2 = 'account/2fa/main.protected.html.twig';
}
else {
$twoFactorView = 'account/2fa/app/manage.enable.html.twig';
$twoFactorView2 = 'account/2fa/email/manage.enable.html.twig';
}
return [$twoFactorView, $twoFactorView2];
}
public function enable(int $type): void {
$this->account->setCustomField('2fa_type', $type);
}
public function disable(): void
{
global $db;
$this->account->setCustomField('2fa_type', self::TYPE_NONE);
if ($db->hasColumn('accounts', 'secret')) {
$this->account->setCustomField('secret', null);
}
$this->account->setCustomField('2fa_secret', '');
}
public function isActive(?int $authType = null): bool {
if ($authType !== null) {
return $this->authType === $authType;
}
return $this->authType != self::TYPE_NONE;
}
public function getAuthType(): int {
return $this->authType;
}
public function getAuthGateway(): AppAuthGateway|EmailAuthGateway {
return $this->authGateway;
}
public function hasRecentEmailCode($since = self::EMAIL_CODE_VALID_UNTIL): bool {
return AccountEMailCode::where('account_id', '=', $this->account->getId())->where('created_at', '>', time() - $since)->first() !== null;
}
public function deleteOldCodes(): void {
AccountEMailCode::where('account_id', '=', $this->account->getId())->delete();
}
public function appInitTOTP(string $secret): TOTP
{
$otp = TOTP::createFromSecret($secret);
$otp->setLabel($this->account->getEmail());
$otp->setIssuer(configLua('serverName'));
return $otp;
}
public function appDisplayEnable(string $secret, ?TOTP $otp = null, array $errors = []): void
{
global $twig;
if ($otp === null) {
$otp = $this->appInitTOTP($secret);
}
$grCodeUri = $otp->getQrCodeUri(
'https://api.qrserver.com/v1/create-qr-code/?data=[DATA]&size=200x200&ecc=M',
'[DATA]'
);
$twig->display('account/2fa/app/enable.html.twig', [
'grCodeUri' => $grCodeUri,
'secret' => $secret,
'errors' => $errors,
]);
}
public function resendEmailCode(): void
{
global $twig;
$newCode = generateRandomString(6, true, false, true);
AccountEMailCode::create([
'account_id' => $this->account->getId(),
'code' => $newCode,
'created_at' => time(),
]);
$mailBody = $twig->render('mail.account.2fa.email-code.html.twig', [
'code' => $newCode,
]);
if (!_mail($this->account->getEMail(), configLua('serverName') . ' - Requested Authentication Email Code', $mailBody)) {
error('An error occurred while sending email. For Admin: More info can be found in system/logs/mailer-error.log');
}
}
}

View File

@@ -183,7 +183,7 @@ class Validator
return false;
}
// installer doesn't know config.php yet
// installer doesn't know settings yet
// that's why we need to ignore the nulls
if(defined('MYAAC_INSTALL')) {
$minLength = 4;
@@ -207,21 +207,15 @@ class Validator
return false;
}
if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM- [ ] '") != $length)
if(strspn($name, "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM ") != $length)
{
self::$lastError = "Invalid name format. Use only A-Z, spaces and '.";
self::$lastError = "This name contains invalid letters. Please use only A-Z, a-z and space!";
return false;
}
if(preg_match('/ {2,}/', $name))
{
self::$lastError = 'Invalid character name format. Use only A-Z and no double spaces.';
return false;
}
if(!preg_match("/[A-z ']/", $name))
{
self::$lastError = "Invalid name format. Use only A-Z, spaces and '.";
self::$lastError = 'Invalid character name format. Use only A-Z, a-z and no double spaces.';
return false;
}
@@ -230,17 +224,23 @@ class Validator
/**
* Validate new character name.
* Name lenght must be 3-25 chars
* Name length must be 3-25 chars
*
* @param string $name Name to check
* @return bool Is name valid?
*/
public static function newCharacterName($name)
{
global $db, $config;
global $db;
$name = trim($name);
$name_lower = strtolower($name);
if(strlen($name) < 1) {
self::$lastError = 'Please enter a name.';
return false;
}
$first_words_blocked = array_merge(["'", '-'], setting('core.create_character_name_blocked_prefix'));
foreach($first_words_blocked as $word) {
if($word == substr($name_lower, 0, strlen($word))) {
@@ -249,11 +249,6 @@ class Validator
}
}
if(str_ends_with($name_lower, "'") || str_ends_with($name_lower, "-")) {
self::$lastError = 'Your name contains illegal characters.';
return false;
}
if(substr($name_lower, 1, 1) == ' ') {
self::$lastError = 'Your name contains illegal space.';
return false;
@@ -265,11 +260,36 @@ class Validator
}
if(preg_match('/ {2,}/', $name)) {
self::$lastError = 'Invalid character name format. Use only A-Z and numbers 0-9 and no double spaces.';
self::$lastError = 'Invalid character name format. Use only A-Z and no double spaces.';
return false;
}
if(strtolower($config['lua']['serverName']) == $name_lower) {
if (substr($name[0], 0, 1) !== strtoupper(substr($name[0], 0, 1))) {
self::$lastError = 'The first letter of a name has to be a capital letter.';
return false;
}
foreach (explode(' ', $name) as $word) {
$wordCut = substr($word, 1, strlen($word));
$hasUpperCase = preg_match('/[A-Z]/', $wordCut);
if ($hasUpperCase) {
self::$lastError = 'In names capital letters are only allowed at the beginning of a word.';
return false;
}
if (strlen($word) == 1) {
self::$lastError = 'This name contains a word with only one letter. Please use more than one letter for each word.';
return false;
}
$hasVowel = preg_match('/[aeiouAEIOU]/', $word);
if (!$hasVowel) {
self::$lastError = 'This name contains a word without vowels. Please choose another name.';
return false;
}
}
if(strtolower(configLua('serverName')) == $name_lower) {
self::$lastError = 'Your name cannot be same as server name.';
return false;
}

View File

@@ -69,7 +69,6 @@ define('HOOK_ACCOUNT_LOGIN_AFTER_PASSWORD', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_REMEMBER_ME', ++$i);
define('HOOK_ACCOUNT_LOGIN_AFTER_PAGE', ++$i);
define('HOOK_ACCOUNT_LOGIN_POST', ++$i);
define('HOOK_ACCOUNT_LOGIN_PRE', ++$i);
define('HOOK_ACCOUNT_LOST_CHECK_CODE_FINISH_AFTER_PASSWORD', ++$i);
define('HOOK_ACCOUNT_LOST_CHECK_CODE_FINISH_AFTER_PASSWORD_REPEAT', ++$i);
define('HOOK_ACCOUNT_LOST_EMAIL_SET_NEW_PASSWORD_POST', ++$i);

View File

@@ -147,9 +147,6 @@
{% include('buttons.base.html.twig') %}
</form>
<br/>
{{ include('account/2fa/main.html.twig') }}
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_ACCOUNT_LOGS') }}
<a name="Account+Logs" ></a>
<h2>Account Logs</h2>

View File

@@ -1,76 +0,0 @@
<table style="width:100%;">
<tbody>
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<ol>
<li>Open an authenticator app of your choice (e.g. <a
target="_blank"
href="https://support.google.com/accounts/answer/1066447"
rel="noopener noreferrer">Google Authenticator</a>, <a
target="_blank" href="https://www.authy.com/users"
rel="noopener noreferrer">Authy</a>). In the app you
will be asked either to enter a key manually:<br><b>{{ secret }}</b><br>or
to scan the barcode below:<br>
<img alt="QR code" style="margin-top: 15px; margin-bottom: 15px;"
src="{{ grCodeUri }}">
</li>
<li><label for="totp">Enter the verification code you have received from the used
authenticator app:</label><br>
<div style="margin-top: 15px; margin-bottom: 15px;">
<input form="form" id="auth-code" name="auth-code" maxlength="6" autocomplete="off">
{% if errors|length > 0 %}
<br/>
<div class="FormFieldError">{{ errors[0] }}</div>
{% endif %}
</div>
</li>
<li>Click on "Continue" to connect the authenticator app to your
Tibia account.
</li>
</ol>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
<br>
<table style="width: 100%;">
<tbody>
<tr align="center" valign="top">
<td>
<form id="form" method="post" action="{{ getLink('account/2fa/app/enable') }}">
<input type="hidden" name="action" value="link">
{{ csrf() }}
{% set button_color = 'green' %}
{% set button_name = 'Continue' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_color = 'blue' %}
{% set button_name = 'Cancel' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
</tr>
</tbody>
</table>

View File

@@ -1,101 +0,0 @@
{% set title = 'Warning' %}
{% set background = config('darkborder') %}
{% set content %}
<table style="width:100%;">
<tbody>
<tr>
<td>
<span class="red"><b>Please read this warning carefully as it contains important security information! If you skip this message, you might lose your {{ config.lua.serverName }} account!</b></span><br><br>
<p>Before you connect your account with an authenticator app, you will be asked to
enter your recovery key. If you do not have a valid recovery key, you need to
order a new one before you can connect your account with an authenticator.</p>
<p>Why?<br>The recovery key is the only way to unlink the authenticator app from
your {{ config.lua.serverName }} account in various cases, among others, if:</p>
<ul style="list-style-type:square">
<li>you lose your device (mobile phone, tablet, etc.) with the authenticator
app
</li>
<li>the device with the authenticator app does not work anymore</li>
<li>the device with the authenticator app gets stolen</li>
<li>you delete the authenticator app from your device and reinstall it</li>
<li>your device is reset for some reason</li>
</ul>
<p></p>
<p>Please note that the authenticator app data is not saved on your device's account
(e.g. Google or iTunes sync) even if you have app data backup&amp;synchronisation
activated in the settings of your device!</p>
<p>In all these scenarios, the recovery key is the only way to get access to your
{{ config.lua.serverName }} account. Note that not even customer support will be able to help you in
these cases if you do not have a valid recovery key.<br>For this reason, make
sure to store your recovery key always in a safe place!</p><br>Do you have a
valid recovery key and would like to request the email with the confirmation key to
start connecting your {{ config.lua.serverName }} account to an authenticator app?<br><br><b>Enter your
recovery key:</b><br/>
<div style="margin-top: 15px; margin-bottom: 15px;">
{% if newRecoveryKeyFormat %}
<input form="form" class="UpperCaseInput" name="key1" value="" size="5" maxlength="5" autocomplete="off"> -
<input form="form" class="UpperCaseInput" name="key2" value="" size="5" maxlength="5" autocomplete="off"> - <input form="form" class="UpperCaseInput" name="key3" value="" size="5" maxlength="5" autocomplete="off"> -
<input form="form" class="UpperCaseInput" name="key4" value="" size="5" maxlength="5" autocomplete="off">
{% else %}
<input form="form" class="UpperCaseInput" name="key" value="" autocomplete="off">
{% endif %}
{% if errors|length > 0 %}
<br/>
<div class="FormFieldError">{{ errors[0] }}</div>
{% endif %}
</div>
</td>
</tr>
</tbody>
</table>
{% endset %}
{% include 'tables.headline.html.twig' %}
<br>
<table style="width:100%;">
<tbody>
<tr align="center">
<td>
<form id="form" action="{{ getLink('account/2fa/app/enable') }}" method="post" style="padding:0;margin:0;">
<input type="hidden" name="action" value="request" />
{{ csrf() }}
{% set button_name = 'Request' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/register') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_name = 'Order Recovery Key' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_name = 'Cancel Request' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
</tr>
</tbody>
</table>
<style>
.UpperCaseInput {
text-transform: uppercase;
}
</style>

View File

@@ -1,64 +0,0 @@
{% set title = 'Enter Authenticator App Token' %}
{% set background = config('darkborder') %}
{% set content %}
<table style="width:100%;">
<tbody>
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>Enter the verification code generated by the app:<br>
<div style="margin-top: 15px; margin-bottom: 15px;">
<div class="LabelV200" style="float:left;">Authenticator App Token:</div>
<input form="form" id="auth-code" name="auth-code" maxlength="6" autocomplete="off" required autofocus></div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
{% endset %}
{% include 'tables.headline.html.twig' %}
<br/>
<table style="width: 100%;">
<tbody>
<tr align="center" valign="top">
<td>
<form id="form" action="{{ getLink('account/manage') }}" method="post">
{{ csrf() }}
<input type="hidden" name="account_login" value="{{ account_login ?? '' }}" />
<input type="hidden" name="password_login" value="{{ password_login ?? '' }}" />
{% if remember_me %}
<input type="hidden" name="remember_me" value="true" />
{% endif %}
<input type="hidden" name="step" value="verify">
{% set button_color = 'green' %}
{% set button_name = 'Continue' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post"
style="padding:0;margin:0;">
{{ csrf() }}
{% set button_color = 'blue' %}
{% set button_name = 'Cancel' %}
{{ include('buttons.base.html.twig') }}
</form>
</td>
</tr>
</tbody>
</table>

View File

@@ -1,25 +0,0 @@
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<div style="float: right; width: 135px;">
<form action="{{ getLink('account/2fa/app/disable') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_name = 'Unlink' %}
{{ include('buttons.base.html.twig') }}
</form>
</div>
<b>Your Tibia account is <span style="color: green">connected</span> to an authenticator app.</b>
<p>If you do not want to use an authenticator app any longer, you can "Unlink" the authenticator
App. Note, however, an authenticator app is an important security feature which helps to
prevent any unauthorized access to your Tibia account.</p></td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>

View File

@@ -1,36 +0,0 @@
<tr>
<td>
<div class="TableShadowContainerRightTop">
<div class="TableShadowRightTop" style="background-image:url({{ template_path }}/images/global/content/table-shadow-rt.gif);"></div>
</div>
<div class="TableContentAndRightShadow" style="background-image:url({{ template_path }}/images/global/content/table-shadow-rm.gif);">
<div class="TableContentContainer">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody><tr>
<td class="LabelV"><b>Connect your {{ config.lua.serverName }} account to an authenticator app!</b>
<div style="float: right; font-size: 1px;">
<form action="{{ getLink('account/2fa/app/enable') }}" method="post" style="margin: 0; padding: 0;">
{{ csrf() }}
{% set button_name = 'Request' %}
{% include('buttons.base.html.twig') %}
</form>
</div>
</td>
</tr>
<tr>
<td>
<p>As a first step to connect an <b>authenticator app</b> to your account, click on "Request"! An email with a confirmation key will be sent to the email address assigned to your account.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="TableShadowContainer">
<div class="TableBottomShadow" style="background-image:url({{ template_path }}/images/global/content/table-shadow-bm.gif);">
<div class="TableBottomLeftShadow" style="background-image:url({{ template_path }}/images/global/content/table-shadow-bl.gif);"></div>
<div class="TableBottomRightShadow" style="background-image:url({{ template_path }}/images/global/content/table-shadow-br.gif);"></div>
</div>
</div>
</td>
</tr>

View File

@@ -1,108 +0,0 @@
{% set title = 'Deactivate Email Code Authentication' %}
{% set content %}
<table style="width:100%;">
<tbody>
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>To disable <b>two-factor email code authentication</b> for your account, enter the
received <b>email code</b> below. Note, however, that <b>email code authentication</b>
is an important security feature which helps to prevent any unauthorized access to your
Tibia account.
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
<tr>
<td>
<div class="TableContentContainer">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<div style="float: right;">
<form
action="{{ getLink('account/2fa/email/resend-code') }}"
method="post"
style="padding:0;margin:0;"
>
{{ csrf() }}
{% set button_name = 'Resend Email Code' %}
{{ include('buttons.base.html.twig') }}
</form>
</div>
An <b>email code</b> has already been sent to the email address assigned to your
account.
Please check your email account's spam/junk filter and make sure that your mailbox is
not
full.<br>In case you need a new email code, you can request one by clicking on "Resend
Email
Code".
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
<tr>
<td>
<div class="TableContentContainer">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>To complete the deactivation of <b>email code authentication</b>, please enter the <b>email
code</b> you received at the email address assigned to your account.
<div style="margin-top: 15px; margin-bottom: 15px;">
<div class="LabelV150 {{ wrongCode ? 'red' : '' }}" style="float:left;"><label
for="email-code">Email Code:</label></div>
<input form="form" id="auth-code" name="email-code" maxlength="15"
autocomplete="off">
{% if wrongCode %}
<br/>
<div class="LabelV150" style="float:left;">&nbsp; </div>
<div class="FormFieldError">Invalid email code!</div>
{% endif %}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
{% endset %}
{% include 'tables.headline.html.twig' %}
<table style="width: 100%;">
<tbody>
<tr align="center" valign="top">
<td>
<form id="form" method="post" action="{{ getLink('account/2fa/email/disable') }}">
{{ csrf() }}
<input type="hidden" name="save" value="1">
{% set button_name = 'Continue' %}
{% set button_color = 'green' %}
{{ include('buttons.submit.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_color = 'blue' %}
{{ include('buttons.back.html.twig') }}
</form>
</td>
</tr>
</tbody>
</table>

View File

@@ -1,108 +0,0 @@
{% set title = 'Activate Email Code Authentication' %}
{% set content %}
<table style="width:100%;">
<tbody>
<tr>
<td>
<div class="TableContentContainer">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>Enter the email code below to enable <b>two-factor email code authentication</b>. Note
that this code is only valid for 24 hours.<br><br>
<div class="AttentionSign"><img src="{{ template_path }}/images/global/content/attentionsign.gif"></div>
<b>Note:</b> Once you have email code authentication enabled, an <b>email code</b> will be
sent to the email address assigned to your account whenever you try to log in to the Tibia
client or the {{ config.lua.serverName }} website. In order to log in, you will need to enter the <b>most recent
email code</b> you have received.
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
<tr>
<td>
<div class="TableContentContainer">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<div style="float: right;">
<form action="{{ getLink('account/2fa/email/resend-code') }}"
method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% if account_logged is defined %}
<input type="hidden" name="account_logged" value="{{ account_logged.getId() }}">
{% endif %}
{% set button_name = 'Resend Email Code' %}
{% include('buttons.base.html.twig') %}
</form>
</div>
An <b>email code</b> has already been sent to the email address assigned to your account.
Please check your email account's spam/junk filter and make sure that your mailbox is not
full.<br>In case you need a new email code, you can request one by clicking on "Resend Email
Code".
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
<tr>
<td>
<div class="TableContentContainer">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>To complete the activation of email code authentication for your Tibia account, please enter
the email code you received at the email address assigned to your account.
<div style="margin-top: 15px; margin-bottom: 15px;">
<div class="LabelV150 {{ wrongCode ? 'red' : '' }}" style="float:left;">Email Code:</div>
<input form="form" name="auth-code" maxlength="6" autocomplete="off">
{% if wrongCode %}
<br/>
<div class="LabelV150" style="float:left;">&nbsp; </div>
<div class="FormFieldError">Invalid email code!</div>
{% endif %}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
{% endset %}
{% include 'tables.headline.html.twig' %}
<br/>
<table style="width: 100%;">
<tbody>
<tr align="center" valign="top">
<td>
<form id="form" action="{{ getLink('account/2fa/email/enable') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
<input type="hidden" name="save" value="1">
{% set button_color = 'green' %}
{{ include('buttons.submit.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_color = 'blue' %}
{{ include('buttons.back.html.twig') }}
</form>
</td>
</tr>
</tbody>
</table>

View File

@@ -1,91 +0,0 @@
{% set title = 'Enter Email Code' %}
{% set content %}
<table style="width:100%;">
<tbody>
<tr>
<td>
<div class="TableContentContainer">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<div style="float: right;">
<form
action="{{ getLink('account/2fa/email/resend-code') }}"
method="post"
style="padding:0;margin:0;"
>
{{ csrf() }}
{% set button_name = 'Resend E-Mail Code' %}
{{ include('buttons.base.html.twig') }}
</form>
</div>
An <b>E-Mail code</b> has already been sent to the E-Mail address assigned to your account.
Please check your E-Mail account's spam/junk filter and make sure that your mailbox is not
full.<br>In case you need a new E-Mail code, you can request one by clicking on "Resend E-Mail Code".
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
<tr>
<td>
<div class="TableContentContainer">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td><b>E-Mail code authentication is enabled for your account.</b><br><br>Please enter the <b>most
recent E-Mail code</b> you have received in order to log in.<br>
<div style="margin-top: 15px; margin-bottom: 15px;">
<div class="LabelV150 {{ wrongCode ? 'red' : '' }}" style="float:left;"><label for="email-code">E-Mail Code:</label></div>
<input form="form" id="auth-code" name="auth-code" maxlength="15" autocomplete="off" required autofocus>
{% if wrongCode %}
<br/>
<div class="LabelV150" style="float:left;">&nbsp; </div>
<div class="FormFieldError">Invalid E-Mail code!</div>
{% endif %}
</div>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>
</tbody>
</table>
{% endset %}
{% include 'tables.headline.html.twig' %}
<table style="width: 100%;">
<tbody>
<tr align="center" valign="top">
<td>
<form id="form" method="post" action="{{ getLink('account/manage') }}">
{{ csrf() }}
<input type="hidden" name="account_login" value="{{ account_login ?? '' }}" />
<input type="hidden" name="password_login" value="{{ password_login ?? '' }}" />
{% if remember_me %}
<input type="hidden" name="remember_me" value="true" />
{% endif %}
<input type="hidden" name="step" value="verify">
{% set button_name = 'Continue' %}
{% set button_color = 'green' %}
{{ include('buttons.submit.html.twig') }}
</form>
</td>
<td>
<form action="{{ getLink('account/manage') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_color = 'blue' %}
{{ include('buttons.back.html.twig') }}
</form>
</td>
</tr>
</tbody>
</table>

View File

@@ -1,26 +0,0 @@
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td>
<div style="float: right; width: 135px;">
<form action="{{ getLink('account/2fa/email/disable') }}" method="post" style="padding:0;margin:0;">
{{ csrf() }}
{% set button_name = 'Disable' %}
{{ include('buttons.base.html.twig') }}
</form>
</div>
<b>Two-Factor Email Code Authentication <span style="color: green">Enabled</span>!</b>
<p>To disable <b>email code authentication</b>, click on the "Disable" button.</p>
<!--p>You will have to confirm the deactivation by entering an <b>email code</b> which will be sent
to the email address assigned to your account.</p-->
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>

View File

@@ -1,37 +0,0 @@
<tr>
<td>
<div class="TableShadowContainerRightTop">
<div class="TableShadowRightTop" style="background-image:url({{ template_path }}/images/global/content/table-shadow-rt.gif);"></div>
</div>
<div class="TableContentAndRightShadow" style="background-image:url({{ template_path }}/images/global/content/table-shadow-rm.gif);">
<div class="TableContentContainer">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td class="LabelV"><b>Enable email code authentication for your account!</b>
<div style="float: right; font-size: 1px;">
<form action="{{ getLink('account/2fa/email/enable') }}" method="post" style="margin: 0; padding: 0;">
{{ csrf() }}
{% set button_name = 'Request' %}
{% include('buttons.base.html.twig') %}
</form>
</div>
</td>
</tr>
<tr>
<td>
<p>As a first step to enable <b>email code authentication</b> for your account, click on "Request"! An <b>email code</b> will be sent to the email address assigned to your account. You will be asked to enter this <b>email code</b> on the next page within 24 hours.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="TableShadowContainer">
<div class="TableBottomShadow" style="background-image:url({{ template_path }}/images/global/content/table-shadow-bm.gif);">
<div class="TableBottomLeftShadow" style="background-image:url({{ template_path }}/images/global/content/table-shadow-bl.gif);"></div>
<div class="TableBottomRightShadow" style="background-image:url({{ template_path }}/images/global/content/table-shadow-br.gif);"></div>
</div>
</div>
</td>
</tr>

View File

@@ -1,12 +0,0 @@
{% set title = 'Two-Factor Authentication' %}
{% set content %}
<table style="width:100%;">
<tbody>
{{ include(twoFactorViews[0]) }}
{{ include(twoFactorViews[1]) }}
</tbody>
</table>
{% endset %}
{% include('tables.headline.html.twig') %}
<br/>

View File

@@ -1,22 +0,0 @@
{% if logged and account_logged.getCustomField('2fa_type') == 1 %}
{% set header = 'Two-Factor Email Code Authentication' %}
{% set text = 'Your account is currently protected by email code authentication. If you prefer to use a <strong>two-factor authentication app</strong>, you have to "Disable" email code authentication first.' %}
{% else %}
{% set header = 'Two-Factor App Code Authentication' %}
{% set text = 'Your account is currently protected by an authenticator app. If you prefer to use the <strong>two-factor email code authentication</strong>, you have to "Unlink" the authenticator app first.' %}
{% endif %}
<tr>
<td>
<div class="TableContentContainer ">
<table class="TableContent" width="100%" style="border:1px solid #faf0d7;">
<tbody>
<tr>
<td><b>{{ header|raw }}</b>
<p>{{ text|raw }}</p>
</td>
</tr>
</tbody>
</table>
</div>
</td>
</tr>

View File

@@ -9,7 +9,10 @@
<div class="box">
<div class="box-body">
<button name="save" type="submit" class="btn btn-primary">Save</button>
<button form="reset-settings-form" name="reset" type="submit" class="btn btn-warning position-absolute" style="right: 0; top: 0" onclick="return confirm('Are you sure? This will clear all settings for this plugin!')">Reset</button>
</div>
<br/>
{{ settingsParsed|raw }}
</div>
@@ -18,6 +21,12 @@
</form>
</div>
</div>
<form id="reset-settings-form" method="post" action="{{ constant('ADMIN_URL') }}?p=settings&plugin={{ pluginName }}">
{{ csrf() }}
<input type="hidden" name="reset" value="1">
</form>
<style>
.setting-default {
white-space: pre-wrap;
@@ -95,12 +104,12 @@
.on('change input', function(){
const disable = $(this).serialize() === $(this).data('serialized');
$(this)
.find('input:submit, button:submit')
.find('button[name="save"]')
.prop('disabled', disable)
.prop('title', disable ? noChangesText : '')
;
})
.find('input:submit, button:submit')
.find('button[name="save"]')
.prop('disabled', true)
.prop('title', noChangesText)
;
@@ -123,7 +132,7 @@
let $settings = $('#settings');
$settings.data('serialized', $settings.serialize());
$settings
.find('input:submit, button:submit')
.find('button[name="save"]')
.prop('disabled', true)
.prop('title', noChangesText);
},

View File

@@ -1,9 +0,0 @@
Dear {{ config.lua.serverName}} player,
<br/><br/>
Your account is protected by email code authentication, and you requested a new email code:
<br/><br/>
<h1><strong>{{ code }}</strong></h1>
<br/>
Note that the code is only valid for 24 hours.
<br/><br/>
Kind Regards,

View File

@@ -1,5 +0,0 @@
Dear {{ config.lua.serverName}} player,<br/>
<br/>
A <strong>wrong two-factor authentication code</strong> was entered for your {{ config.lua.serverName}} account. If you simply mistyped the code, please try again.<br/>
<br/>
However, if this was <strong>not you</strong>, someone else may be trying to access your account. Since they already know your password, we strongly recommend that you <strong>change your password immediately</strong>.

View File

@@ -290,9 +290,6 @@
{% endset %}
{% include 'tables.headline.html.twig' %}
<br/>
{{ include('account/2fa/main.html.twig') }}
{{ hook('HOOK_ACCOUNT_MANAGE_BEFORE_ACCOUNT_LOGS') }}
<a name="Account+Logs" ></a>
<div class="TopButtonContainer">

View File

@@ -943,14 +943,6 @@ img {
font-size: 8pt;
color: red;
}
.AttentionSign img {
float: left;
top: 3px;
left: 8px;
width: 15px;
height: 13px;
margin-right: 5px;
}
.SmallBox {
position: relative;
font-size: 1px;

View File

@@ -63,10 +63,7 @@ else if(isset($_GET['email']))
}
else if(isset($_GET['name']))
{
$name = $_GET['name'];
if(!admin()) {
$name = strtolower(stripslashes($name));
}
$name = trim(stripslashes($_GET['name']));
if(!Validator::characterName($name)) {
error_(Validator::getLastError());
@@ -81,7 +78,12 @@ else if(isset($_GET['name']))
error_($errors['name']);
}
success_('Good. Your name will be:<br /><b>' . (admin() ? $name : ucwords($name)) . '</b>');
$extraText = '';
if (admin()) {
$extraText = "<br/>Note: You are logged in as admin, so you can create almost any name without rules.";
}
success_("Good. Your name will be:<br /><b>$name</b>$extraText");
}
else if(isset($_GET['password']) && isset($_GET['password_confirm'])) {
$password = $_GET['password'];