mirror of
https://github.com/slawkens/myaac.git
synced 2026-02-06 21:26:22 +01:00
Compare commits
36 Commits
develop
...
feature/2f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed88f9f401 | ||
|
|
c2424df7a4 | ||
|
|
4d2ed93b31 | ||
|
|
7471c49793 | ||
|
|
381d5bb884 | ||
|
|
234e17654b | ||
|
|
1da771e3ca | ||
|
|
4d7fe0bd58 | ||
|
|
e2c9c2bbe0 | ||
|
|
04b37b4356 | ||
|
|
bf70595095 | ||
|
|
668f00e746 | ||
|
|
bbc8bef008 | ||
|
|
5e5fd43233 | ||
|
|
867e3e2c38 | ||
|
|
1975fb8ebe | ||
|
|
a44e2d6ebe | ||
|
|
babd822171 | ||
|
|
21e2eed640 | ||
|
|
2e4a8c3d3d | ||
|
|
9f64d7834f | ||
|
|
7d71bc2fee | ||
|
|
fdd0de8602 | ||
|
|
abee4b3962 | ||
|
|
fbdb6890b9 | ||
|
|
041f58ed11 | ||
|
|
03c7dd0002 | ||
|
|
e435062025 | ||
|
|
ecc9bd4042 | ||
|
|
797377e428 | ||
|
|
96b5df9d74 | ||
|
|
b3dfc56c96 | ||
|
|
96d6e04bd2 | ||
|
|
9146eee327 | ||
|
|
3d97fa0719 | ||
|
|
a66cafceab |
@@ -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 = 50;
|
||||
const DATABASE_VERSION = 51;
|
||||
const TABLE_PREFIX = 'myaac_';
|
||||
define('START_TIME', microtime(true));
|
||||
define('MYAAC_OS', stripos(PHP_OS, 'WIN') === 0 ? 'WINDOWS' : (strtoupper(PHP_OS) === 'DARWIN' ? 'MAC' : 'LINUX'));
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
"symfony/var-dumper": "^6.4",
|
||||
"filp/whoops": "^2.15",
|
||||
"maximebf/debugbar": "1.*",
|
||||
"guzzlehttp/guzzle": "7.9.3"
|
||||
"guzzlehttp/guzzle": "7.9.3",
|
||||
"spomky-labs/otphp": "^11.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.10"
|
||||
|
||||
717
composer.lock
generated
717
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,15 @@ CREATE TABLE IF NOT EXISTS `myaac_account_actions`
|
||||
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`)
|
||||
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `myaac_account_emails_verify`
|
||||
(
|
||||
`id` int NOT NULL AUTO_INCREMENT,
|
||||
|
||||
@@ -98,6 +98,16 @@ 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...');
|
||||
|
||||
104
login.php
104
login.php
@@ -5,6 +5,7 @@ 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';
|
||||
@@ -12,7 +13,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;
|
||||
@@ -108,17 +109,18 @@ switch ($action) {
|
||||
|
||||
case 'login':
|
||||
|
||||
$port = $config['lua']['gameProtocolPort'];
|
||||
$ip = configLua('ip');
|
||||
$port = configLua('gameProtocolPort');
|
||||
|
||||
// default world info
|
||||
$world = [
|
||||
'id' => 0,
|
||||
'name' => $config['lua']['serverName'],
|
||||
'externaladdress' => $config['lua']['ip'],
|
||||
'externaladdress' => $ip,
|
||||
'externalport' => $port,
|
||||
'externaladdressprotected' => $config['lua']['ip'],
|
||||
'externaladdressprotected' => $ip,
|
||||
'externalportprotected' => $port,
|
||||
'externaladdressunprotected' => $config['lua']['ip'],
|
||||
'externaladdressunprotected' => $ip,
|
||||
'externalportunprotected' => $port,
|
||||
'previewstate' => 0,
|
||||
'location' => 'BRA', // BRA, EUR, USA
|
||||
@@ -133,13 +135,12 @@ switch ($action) {
|
||||
|
||||
$inputEmail = $request->email ?? false;
|
||||
$inputAccountName = $request->accountname ?? false;
|
||||
$inputToken = $request->token ?? false;
|
||||
|
||||
$account = Account::query();
|
||||
if ($inputEmail != false) { // login by email
|
||||
if ($inputEmail) { // login by email
|
||||
$account->where('email', $inputEmail);
|
||||
}
|
||||
else if($inputAccountName != false) { // login by account name
|
||||
else if($inputAccountName) { // login by account name
|
||||
$account->where('name', $inputAccountName);
|
||||
}
|
||||
|
||||
@@ -151,13 +152,14 @@ 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 != false ? 'Email' : 'Account name') . ' or password is not correct.');
|
||||
sendError(($inputEmail ? 'Email' : 'Account name') . ' or password is not correct.');
|
||||
}
|
||||
|
||||
$current_password = encrypt((USE_ACCOUNT_SALT ? $account->salt : '') . $request->password);
|
||||
@@ -167,32 +169,30 @@ switch ($action) {
|
||||
sendError($ban_msg);
|
||||
}
|
||||
|
||||
sendError(($inputEmail != false ? 'Email' : 'Account name') . ' or password is not correct.');
|
||||
sendError(($inputEmail ? 'Email' : 'Account name') . ' or password is not correct.');
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
$twoFactorAuth = TwoFactorAuth::getInstance($account->id);
|
||||
|
||||
sendError('Two-factor authentication failed, token is wrong.', 6);
|
||||
}
|
||||
}
|
||||
$code = '';
|
||||
if ($twoFactorAuth->isActive()) {
|
||||
if ($twoFactorAuth->getAuthType() === TwoFactorAuth::TYPE_EMAIL) {
|
||||
$code = $request->emailcode ?? false;
|
||||
}
|
||||
else if ($twoFactorAuth->getAuthType() === TwoFactorAuth::TYPE_APP) {
|
||||
$code = $request->token ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
$error = '';
|
||||
$errorCode = 6;
|
||||
if (!$twoFactorAuth->processClientLogin($code, $error, $errorCode)) {
|
||||
$limiter->increment($ip);
|
||||
if ($limiter->exceeded($ip)) {
|
||||
sendError($ban_msg);
|
||||
}
|
||||
|
||||
sendError($error, $errorCode);
|
||||
}
|
||||
|
||||
$limiter->reset($ip);
|
||||
@@ -220,46 +220,6 @@ 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');
|
||||
|
||||
@@ -268,7 +228,7 @@ switch ($action) {
|
||||
if (!fieldExist('istutorial', 'players')) {
|
||||
$sessionKey .= "\n";
|
||||
}
|
||||
$sessionKey .= ($accountHasSecret && strlen($accountSecret) > 5) ? $inputToken : '';
|
||||
$sessionKey .= ($twoFactorAuth->isActive() && strlen($account->{'2fa_secret'}) > 5) ? $account->{'2fa_secret'} : '';
|
||||
|
||||
// this is workaround to distinguish between TFS 1.x and otservbr
|
||||
// TFS 1.x requires the number in session key
|
||||
|
||||
@@ -4,7 +4,6 @@ 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 = '';
|
||||
|
||||
@@ -736,17 +736,11 @@ 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();
|
||||
}
|
||||
|
||||
// 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']);
|
||||
AccountModel::where('id', $this->data['id'])->update([$field => $value]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
8
system/migrations/51-account_email_codes.sql
Normal file
8
system/migrations/51-account_email_codes.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
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;
|
||||
36
system/migrations/51.php
Normal file
36
system/migrations/51.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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'));
|
||||
}
|
||||
};
|
||||
|
||||
$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');
|
||||
}
|
||||
};
|
||||
26
system/pages/account/2fa/app/disable.php
Normal file
26
system/pages/account/2fa/app/disable.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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.'
|
||||
]);
|
||||
105
system/pages/account/2fa/app/enable.php
Normal file
105
system/pages/account/2fa/app/enable.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?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,
|
||||
]
|
||||
);
|
||||
41
system/pages/account/2fa/base.php
Normal file
41
system/pages/account/2fa/base.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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;
|
||||
}
|
||||
34
system/pages/account/2fa/email/disable.php
Normal file
34
system/pages/account/2fa/email/disable.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?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.'
|
||||
]
|
||||
);
|
||||
51
system/pages/account/2fa/email/enable.php
Normal file
51
system/pages/account/2fa/email/enable.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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]);
|
||||
32
system/pages/account/2fa/email/resend-code.php
Normal file
32
system/pages/account/2fa/email/resend-code.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?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');
|
||||
@@ -17,6 +17,10 @@ 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',
|
||||
@@ -30,3 +34,11 @@ 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);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
use MyAAC\RateLimit;
|
||||
use MyAAC\TwoFactorAuth\TwoFactorAuth;
|
||||
|
||||
defined('MYAAC') or die('Direct access not allowed!');
|
||||
|
||||
@@ -52,8 +53,18 @@ 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 {
|
||||
session_regenerate_id();
|
||||
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('password', encrypt((USE_ACCOUNT_SALT ? $account_logged->getCustomField('salt') : '') . $login_password));
|
||||
if($remember_me) {
|
||||
setSession('remember_me', true);
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
* @copyright 2019 MyAAC
|
||||
* @link https://my-aac.org
|
||||
*/
|
||||
|
||||
use MyAAC\TwoFactorAuth\TwoFactorAuth;
|
||||
|
||||
defined('MYAAC') or die('Direct access not allowed!');
|
||||
|
||||
$title = 'Account Management';
|
||||
@@ -116,6 +119,8 @@ $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,
|
||||
));
|
||||
|
||||
@@ -37,7 +37,7 @@ else
|
||||
if($points >= setting('core.account_generate_new_reckey_price'))
|
||||
{
|
||||
$show_form = false;
|
||||
$new_rec_key = generateRandomString(10, false, true, true);
|
||||
$new_rec_key = generateRecoveryKey();
|
||||
|
||||
$mailBody = $twig->render('mail.account.register.html.twig', array(
|
||||
'recovery_key' => $new_rec_key
|
||||
|
||||
@@ -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 = generateRandomString(10, false, true, true);
|
||||
$new_rec_key = generateRecoveryKey();
|
||||
|
||||
$account_logged->setCustomField("key", $new_rec_key);
|
||||
$account_logged->logAction('Generated recovery key.');
|
||||
|
||||
14
system/src/Models/AccountEMailCode.php
Normal file
14
system/src/Models/AccountEMailCode.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?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'];
|
||||
|
||||
}
|
||||
19
system/src/TwoFactorAuth/Gateway/AppAuthGateway.php
Normal file
19
system/src/TwoFactorAuth/Gateway/AppAuthGateway.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
12
system/src/TwoFactorAuth/Gateway/BaseAuthGateway.php
Normal file
12
system/src/TwoFactorAuth/Gateway/BaseAuthGateway.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace MyAAC\TwoFactorAuth\Gateway;
|
||||
|
||||
class BaseAuthGateway
|
||||
{
|
||||
protected \OTS_Account $account;
|
||||
|
||||
public function __construct(\OTS_Account $account) {
|
||||
$this->account = $account;
|
||||
}
|
||||
}
|
||||
16
system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php
Normal file
16
system/src/TwoFactorAuth/Gateway/EmailAuthGateway.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace MyAAC\TwoFactorAuth\Interface;
|
||||
|
||||
interface AuthGatewayInterface
|
||||
{
|
||||
public function __construct(\OTS_Account $account);
|
||||
public function verifyCode(string $code): bool;
|
||||
}
|
||||
270
system/src/TwoFactorAuth/TwoFactorAuth.php
Normal file
270
system/src/TwoFactorAuth/TwoFactorAuth.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ 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);
|
||||
|
||||
@@ -147,6 +147,9 @@
|
||||
{% 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>
|
||||
|
||||
76
system/templates/account/2fa/app/enable.html.twig
Normal file
76
system/templates/account/2fa/app/enable.html.twig
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
<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>
|
||||
101
system/templates/account/2fa/app/enable.warning.html.twig
Normal file
101
system/templates/account/2fa/app/enable.warning.html.twig
Normal file
@@ -0,0 +1,101 @@
|
||||
{% 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&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>
|
||||
64
system/templates/account/2fa/app/login.html.twig
Normal file
64
system/templates/account/2fa/app/login.html.twig
Normal file
@@ -0,0 +1,64 @@
|
||||
{% 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>
|
||||
25
system/templates/account/2fa/app/manage.connected.html.twig
Normal file
25
system/templates/account/2fa/app/manage.connected.html.twig
Normal file
@@ -0,0 +1,25 @@
|
||||
<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>
|
||||
36
system/templates/account/2fa/app/manage.enable.html.twig
Normal file
36
system/templates/account/2fa/app/manage.enable.html.twig
Normal file
@@ -0,0 +1,36 @@
|
||||
<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>
|
||||
108
system/templates/account/2fa/email/disable.html.twig
Normal file
108
system/templates/account/2fa/email/disable.html.twig
Normal file
@@ -0,0 +1,108 @@
|
||||
{% 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;"> </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>
|
||||
108
system/templates/account/2fa/email/enable.html.twig
Normal file
108
system/templates/account/2fa/email/enable.html.twig
Normal file
@@ -0,0 +1,108 @@
|
||||
{% 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;"> </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>
|
||||
91
system/templates/account/2fa/email/login.html.twig
Normal file
91
system/templates/account/2fa/email/login.html.twig
Normal file
@@ -0,0 +1,91 @@
|
||||
{% 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;"> </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>
|
||||
@@ -0,0 +1,26 @@
|
||||
<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>
|
||||
37
system/templates/account/2fa/email/manage.enable.html.twig
Normal file
37
system/templates/account/2fa/email/manage.enable.html.twig
Normal file
@@ -0,0 +1,37 @@
|
||||
<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>
|
||||
12
system/templates/account/2fa/main.html.twig
Normal file
12
system/templates/account/2fa/main.html.twig
Normal file
@@ -0,0 +1,12 @@
|
||||
{% 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/>
|
||||
22
system/templates/account/2fa/main.protected.html.twig
Normal file
22
system/templates/account/2fa/main.protected.html.twig
Normal file
@@ -0,0 +1,22 @@
|
||||
{% 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>
|
||||
9
system/templates/mail.account.2fa.email-code.html.twig
Normal file
9
system/templates/mail.account.2fa.email-code.html.twig
Normal file
@@ -0,0 +1,9 @@
|
||||
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,
|
||||
@@ -0,0 +1,5 @@
|
||||
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>.
|
||||
@@ -290,6 +290,9 @@
|
||||
{% 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">
|
||||
|
||||
@@ -943,6 +943,14 @@ 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;
|
||||
|
||||
Reference in New Issue
Block a user