[false],
'signup' => [false],
'signout' => [true],
'forgotpassword' => [false],
'forgotusername' => [false]
);
protected $user = '';
protected $error = '';
protected $next = '';
protected $lvTabs = [];
protected $banned = [];
protected $_get = array(
'token' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'next' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW],
);
protected $_post = array(
'username' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'password' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkTextLine'],
'c_password' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkTextLine'],
'token' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW],
'remember_me' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\AccountPage::rememberCallback'],
'email' => ['filter' => FILTER_SANITIZE_EMAIL]
);
public function __construct($pageCall, $pageParam)
{
if ($pageParam)
$this->category = [$pageParam];
parent::__construct($pageCall, $pageParam);
if ($pageParam)
{
// requires auth && not authed
if ($this->validCats[$pageParam][0] && !User::isLoggedIn())
$this->forwardToSignIn('account='.$pageParam);
// doesn't require auth && authed
else if (!$this->validCats[$pageParam][0] && User::isLoggedIn())
header('Location: ?account', true, 302); // goto dashboard
}
}
protected static function rememberCallback($val)
{
return $val == 'yes' ? $val : null;
}
protected function generateContent()
{
if (!$this->category)
{
$this->createDashboard();
return;
}
switch ($this->category[0])
{
case 'forgotpassword':
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
{
if (Cfg::get('ACC_EXT_RECOVER_URL'))
header('Location: '.Cfg::get('ACC_EXT_RECOVER_URL'), true, 302);
else
$this->error();
}
$this->tpl = 'acc-recover';
$this->resetPass = false;
if ($this->createRecoverPass($nStep)) // location-header after final step
header('Location: ?account=signin', true, 302);
$this->head = sprintf(Lang::account('recoverPass'), $nStep);
break;
case 'forgotusername':
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
{
if (Cfg::get('ACC_EXT_RECOVER_URL'))
header('Location: '.Cfg::get('ACC_EXT_RECOVER_URL'), true, 302);
else
$this->error();
}
$this->tpl = 'acc-recover';
$this->resetPass = false;
if ($this->_post['email'])
{
if (!Util::isValidEmail($this->_post['email']))
$this->error = Lang::account('emailInvalid');
else if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_account WHERE email = ?', $this->_post['email']))
$this->error = Lang::account('emailNotFound');
else if ($err = $this->doRecoverUser())
$this->error = $err;
else
$this->text = sprintf(Lang::account('recovUserSent'). $this->_post['email']);
}
$this->head = Lang::account('recoverUser');
break;
case 'signin':
$this->tpl = 'acc-signIn';
$this->next = $this->getNext();
if ($this->_post['username'] || $this->_post['password'])
{
if ($err = $this->doSignIn())
$this->error = $err;
else
header('Location: '.$this->getNext(true), true, 302);
}
else if ($this->_get['token'] && ($_ = DB::Aowow()->selectCell('SELECT `username` FROM ?_account WHERE `status` IN (?a) AND `token` = ? AND `statusTimer` > UNIX_TIMESTAMP()', [ACC_STATUS_RECOVER_USER, ACC_STATUS_OK], $this->_get['token'])))
$this->user = $_;
break;
case 'signup':
if (!Cfg::get('ACC_ALLOW_REGISTER'))
$this->error();
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
{
if (Cfg::get('ACC_EXT_CREATE_URL'))
header('Location: '.Cfg::get('ACC_EXT_CREATE_URL'), true, 302);
else
$this->error();
}
$this->tpl = 'acc-signUp';
$nStep = 1;
if ($this->_post['username'] || $this->_post['password'] || $this->_post['c_password'] || $this->_post['email'])
{
if ($err = $this->doSignUp())
$this->error = $err;
else
{
$nStep = 1.5;
$this->text = sprintf(Lang::account('createAccSent'), $this->_post['email']);
}
}
else if ($this->_get['token'] && ($newId = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE status = ?d AND token = ?', ACC_STATUS_NEW, $this->_get['token'])))
{
$nStep = 2;
DB::Aowow()->query('UPDATE ?_account SET status = ?d, statusTimer = 0, token = 0, userGroups = ?d WHERE token = ?', ACC_STATUS_OK, U_GROUP_NONE, $this->_get['token']);
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (ip, type, count, unbanDate) VALUES (?, 1, ?d + 1, UNIX_TIMESTAMP() + ?d)', User::$ip, Cfg::get('ACC_FAILED_AUTH_COUNT'), Cfg::get('ACC_FAILED_AUTH_BLOCK'));
$this->text = sprintf(Lang::account('accActivated'), $this->_get['token']);
}
else
$this->next = $this->getNext();
$this->head = sprintf(Lang::account('register'), $nStep);
break;
case 'signout':
DB::Aowow()->query('UPDATE ?_account_sessions SET `touched` = ?d, `status` = ?d WHERE `sessionId` = ?', time(), SESSION_LOGOUT, session_id());
User::destroy();
default:
header('Location: '.$this->getNext(true), true, 302);
break;
}
}
protected function generateTitle()
{
$this->title = [Lang::account('title')];
}
protected function generatePath() { }
private function createDashboard()
{
if (!User::isLoggedIn())
$this->forwardToSignIn('account');
$user = DB::Aowow()->selectRow('SELECT * FROM ?_account WHERE `id` = ?d', User::$id);
$bans = DB::Aowow()->select('SELECT ab.*, a.`username`, ab.`id` AS ARRAY_KEY FROM ?_account_banned ab LEFT JOIN ?_account a ON a.`id` = ab.`staffId` WHERE ab.`userId` = ?d', User::$id);
/***********/
/* Infobox */
/***********/
$infobox = [];
$infobox[] = Lang::user('joinDate'). Lang::main('colon').'[tooltip name=joinDate]'. date('l, G:i:s', $user['joinDate']). '[/tooltip][span class=tip tooltip=joinDate]'. date(Lang::main('dateFmtShort'), $user['joinDate']). '[/span]';
$infobox[] = Lang::user('lastLogin').Lang::main('colon').'[tooltip name=lastLogin]'.date('l, G:i:s', $user['prevLogin']).'[/tooltip][span class=tip tooltip=lastLogin]'.date(Lang::main('dateFmtShort'), $user['prevLogin']).'[/span]';
$infobox[] = Lang::account('lastIP').Lang::main('colon').$user['prevIP'];
$infobox[] = Lang::account('email'). Lang::main('colon').$user['email'];
$groups = [];
foreach (Lang::account('groups') as $idx => $key)
if ($idx >= 0 && $user['userGroups'] & (1 << $idx))
$groups[] = (!fMod(count($groups) + 1, 3) ? '[br]' : null).Lang::account('groups', $idx);
$infobox[] = Lang::user('userGroups').Lang::main('colon').($groups ? implode(', ', $groups) : Lang::account('groups', -1));
$infobox[] = Util::ucFirst(Lang::main('siteRep')).Lang::main('colon').User::getReputation();
$this->infobox = '[ul][li]'.implode('[/li][li]', $infobox).'[/li][/ul]';
/*************/
/* Ban Popup */
/*************/
foreach ($bans as $b)
{
if (!($b['typeMask'] & (ACC_BAN_TEMP | ACC_BAN_PERM)) || ($b['end'] && $b['end'] <= time()))
continue;
$this->banned = array(
'by' => [$b['staffId'], $b['username']],
'end' => $b['end'],
'reason' => $b['reason']
);
break; // one is enough
}
/************/
/* Listview */
/************/
$this->forceTabs = true;
// Reputation changelog (params only for comment-events)
if ($repData = DB::Aowow()->select('SELECT action, amount, date AS \'when\', IF(action IN (3, 4, 5), sourceA, 0) AS param FROM ?_account_reputation WHERE userId = ?d', User::$id))
{
foreach ($repData as &$r)
$r['when'] = date(Util::$dateFormatInternal, $r['when']);
$this->lvTabs[] = ['reputationhistory', ['data' => $repData]];
}
// comments
if ($_ = CommunityContent::getCommentPreviews(['user' => User::$id, 'comments' => true]))
{
// needs foundCount for params
// _totalCount: 377,
// note: $WH.sprintf(LANG.lvnote_usercomments, 377),
$this->lvTabs[] = ['commentpreview', array(
'data' => $_,
'hiddenCols' => ['author'],
'onBeforeCreate' => '$Listview.funcBox.beforeUserComments'
)];
}
// replies
if ($_ = CommunityContent::getCommentPreviews(['user' => User::$id, 'replies' => true]))
{
// needs commentid (parentComment) for data
// needs foundCount for params
// _totalCount: 377,
// note: $WH.sprintf(LANG.lvnote_usercomments, 377),
$this->lvTabs[] = ['replypreview', array(
'data' => $_,
'hiddenCols' => ['author']
)];
}
/*
*/
// claimed characters
// profiles
// own screenshots
// own videos
// own comments (preview)
// articles guides..?
// cpmsg change pass messaeg class:failure|success, msg:blabla
}
private function createRecoverPass(&$step)
{
$step = 1;
if ($this->_post['email']) // step 1
{
if (!Util::isValidEmail($this->_post['email']))
$this->error = Lang::account('emailInvalid');
else if (!DB::Aowow()->selectCell('SELECT 1 FROM ?_account WHERE email = ?', $this->_post['email']))
$this->error = Lang::account('emailNotFound');
else if ($err = $this->doRecoverPass())
$this->error = $err;
else
{
$step = 1.5;
$this->text = sprintf(Lang::account('recovPassSent'), $this->_post['email']);
}
}
else if ($this->_get['token']) // step 2
{
$step = 2;
$this->resetPass = true;
$this->token = $this->_get['token'];
}
else if ($this->_post['token'] && $this->_post['email'] && $this->_post['password'] && $this->_post['c_password'])
{
$step = 2;
$this->resetPass = true;
$this->token = $this->_post['token']; // insecure source .. that sucks; but whats the worst that could happen .. this account cannot be recovered for some minutes
if ($err = $this->doResetPass())
$this->error = $err;
else
return true;
}
return false;
}
private function doSignIn()
{
// check username
if (!User::isValidName($this->_post['username']))
return Lang::account('userNotFound');
// check password
if (!User::isValidPass($this->_post['password']))
return Lang::account('wrongPass');
switch (User::authenticate($this->_post['username'], $this->_post['password']))
{
case AUTH_OK:
if (!User::$ip)
return Lang::main('intError');
// reset account status, update expiration
DB::Aowow()->query('UPDATE ?_account SET `prevIP` = IF(`curIp` = ?, `prevIP`, `curIP`), `curIP` = IF(`curIp` = ?, `curIP`, ?), `status` = IF(`status` = ?d, `status`, 0), `statusTimer` = IF(`status` = ?d, `statusTimer`, 0), `token` = IF(`status` = ?d, `token`, "") WHERE LOWER(`username`) = LOWER(?)',
User::$ip, User::$ip, User::$ip,
ACC_STATUS_NEW, ACC_STATUS_NEW, ACC_STATUS_NEW,
$this->_post['username']
);
session_regenerate_id(true); // user status changed => regenerate id
// create new session entry
DB::Aowow()->query('INSERT INTO ?_account_sessions (`userId`, `sessionId`, `created`, `expires`, `touched`, `deviceInfo`, `ip`, `status`) VALUES (?d, ?, ?d, ?d, ?d, ?, ?, ?d)',
User::$id, session_id(), time(), $this->_post['remember_me'] ? 0 : time() + Cfg::get('SESSION_TIMEOUT_DELAY'), time(), User::$agent, User::$ip, SESSION_ACTIVE);
if (User::init()) // reinitialize the user
User::save();
return;
case AUTH_BANNED:
if (User::init())
User::save();
return Lang::account('accBanned');
case AUTH_WRONGUSER:
User::destroy();
return Lang::account('userNotFound');
case AUTH_WRONGPASS:
User::destroy();
return Lang::account('wrongPass');
case AUTH_IPBANNED:
User::destroy();
return sprintf(Lang::account('loginExceeded'), Util::formatTime(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000));
case AUTH_INTERNAL_ERR:
User::destroy();
return Lang::main('intError');
default:
return;
}
}
private function doSignUp()
{
// check username
if (!User::isValidName($this->_post['username'], $e))
return Lang::account($e == 1 ? 'errNameLength' : 'errNameChars');
// check password
if (!User::isValidPass($this->_post['password'], $e))
return Lang::account($e == 1 ? 'errPassLength' : 'errPassChars');
if ($this->_post['password'] != $this->_post['c_password'])
return Lang::account('passMismatch');
// check email
if (!Util::isValidEmail($this->_post['email']))
return Lang::account('emailInvalid');
// check ip
if (!User::$ip)
return Lang::main('intError');
// limit account creation
$ip = DB::Aowow()->selectRow('SELECT `ip`, `count`, `unbanDate` FROM ?_account_bannedips WHERE `type` = 1 AND `ip` = ?', User::$ip);
if ($ip && $ip['count'] >= Cfg::get('ACC_FAILED_AUTH_COUNT') && $ip['unbanDate'] >= time())
{
DB::Aowow()->query('UPDATE ?_account_bannedips SET `count` = `count` + 1, `unbanDate` = UNIX_TIMESTAMP() + ?d WHERE `ip` = ? AND `type` = 1', Cfg::get('ACC_FAILED_AUTH_BLOCK'), User::$ip);
return sprintf(Lang::account('signupExceeded'), Util::formatTime(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000));
}
// username taken
if ($_ = DB::Aowow()->SelectCell('SELECT `username` FROM ?_account WHERE (`username` = ? OR `email` = ?) AND (`status` <> ?d OR (`status` = ?d AND `statusTimer` > UNIX_TIMESTAMP()))', $this->_post['username'], $this->_post['email'], ACC_STATUS_NEW, ACC_STATUS_NEW))
return $_ == $this->_post['username'] ? Lang::account('nameInUse') : Lang::account('mailInUse');
// create..
$token = Util::createHash();
$ok = DB::Aowow()->query('REPLACE INTO ?_account (`login`, `passHash`, `username`, `email`, `joindate`, `curIP`, `locale`, `userGroups`, `status`, `statusTimer`, `token`) VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), ?, ?d, ?d, ?d, ?d, UNIX_TIMESTAMP() + ?d, ?)',
$this->_post['username'],
User::hashCrypt($this->_post['password']),
$this->_post['username'],
$this->_post['email'],
User::$ip,
Lang::getLocale()->value,
U_GROUP_PENDING,
ACC_STATUS_NEW,
Cfg::get('ACC_CREATE_SAVE_DECAY'),
$token
);
if (!$ok)
return Lang::main('intError');
if (!Util::sendMail($this->_post['email'], 'activate-account', [$token], Cfg::get('ACC_RECOVERY_DECAY')))
return Lang::main('intError2', ['send mail']);
if ($id = DB::Aowow()->selectCell('SELECT id FROM ?_account WHERE token = ?', $token))
Util::gainSiteReputation($id, SITEREP_ACTION_REGISTER);
// success:: update ip-bans
if (!$ip || $ip['unbanDate'] < time())
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (ip, type, count, unbanDate) VALUES (?, 1, 1, UNIX_TIMESTAMP() + ?d)', User::$ip, Cfg::get('ACC_FAILED_AUTH_BLOCK'));
else
DB::Aowow()->query('UPDATE ?_account_bannedips SET count = count + 1, unbanDate = UNIX_TIMESTAMP() + ?d WHERE ip = ? AND type = 1', Cfg::get('ACC_FAILED_AUTH_BLOCK'), User::$ip);
}
private function doRecoverPass()
{
if ($_ = $this->initRecovery(ACC_STATUS_RECOVER_PASS, Cfg::get('ACC_RECOVERY_DECAY'), $token))
return $_;
// send recovery mail
if (!Util::sendMail($this->_post['email'], 'reset-password', [$token], Cfg::get('ACC_RECOVERY_DECAY')))
return Lang::main('intError2', ['send mail']);
}
private function doResetPass()
{
if ($this->_post['password'] != $this->_post['c_password'])
return Lang::account('passCheckFail');
if (!Util::isValidEmail($this->_post['email']))
return Lang::account('emailInvalid');
$userData = DB::Aowow()->selectRow('SELECT `id, `passHash` FROM ?_account WHERE `token` = ? AND `email` = ? AND `status` = ?d AND `statusTimer` > UNIX_TIMESTAMP()',
$this->_post['token'],
$this->_post['email'],
ACC_STATUS_RECOVER_PASS
);
if (!$userData)
return Lang::account('emailNotFound'); // assume they didn't meddle with the token
if (!User::verifyCrypt($this->_post['c_password'], $userData['passHash']))
return Lang::account('newPassDiff');
if (!DB::Aowow()->query('UPDATE ?_account SET `passHash` = ?, `status` = ?d WHERE `id` = ?d', User::hashCrypt($this->_post['c_password']), ACC_STATUS_OK, $userData['id']))
return Lang::main('intError');
}
private function doRecoverUser()
{
if ($_ = $this->initRecovery(ACC_STATUS_RECOVER_USER, Cfg::get('ACC_RECOVERY_DECAY'), $token))
return $_;
if (!Util::sendMail($this->_post['email'], 'recover-user', [$token], Cfg::get('ACC_RECOVERY_DECAY')))
return Lang::main('intError2', ['send mail']);
}
private function initRecovery($type, $delay, &$token)
{
if (!$type)
return Lang::main('intError');
// check if already processing
if ($_ = DB::Aowow()->selectCell('SELECT statusTimer - UNIX_TIMESTAMP() FROM ?_account WHERE email = ? AND status <> ?d AND statusTimer > UNIX_TIMESTAMP()', $this->_post['email'], ACC_STATUS_OK))
return sprintf(Lang::account('isRecovering'), Util::formatTime($_ * 1000));
// create new token and write to db
$token = Util::createHash();
if (!DB::Aowow()->query('UPDATE ?_account SET token = ?, status = ?d, statusTimer = UNIX_TIMESTAMP() + ?d WHERE email = ?', $token, $type, $delay, $this->_post['email']))
return Lang::main('intError');
}
private function getNext($forHeader = false)
{
$next = $forHeader ? '.' : '';
if ($this->_get['next'])
$next = $this->_get['next'];
else if (isset($_SERVER['HTTP_REFERER']) && strstr($_SERVER['HTTP_REFERER'], '?'))
$next = explode('?', $_SERVER['HTTP_REFERER'])[1];
if ($forHeader && !$next)
$next = '.';
return ($forHeader && $next != '.' ? '?' : '').$next;
}
}
?>