mirror of
https://github.com/Sarjuuk/aowow.git
synced 2025-11-29 15:58:16 +08:00
Template/Update (Part 46 - II)
* account management rework: Signup functionality
This commit is contained in:
73
endpoints/account/activate.php
Normal file
73
endpoints/account/activate.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
/*
|
||||
* accessed via activation email link
|
||||
* empty page with status box
|
||||
*/
|
||||
|
||||
class AccountActivateResponse extends TemplateResponse
|
||||
{
|
||||
protected string $template = 'text-page-generic';
|
||||
protected string $pageName = 'activate';
|
||||
|
||||
protected array $expectedGET = array(
|
||||
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{40}$/']]
|
||||
);
|
||||
|
||||
private bool $success = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if (!Cfg::get('ACC_ALLOW_REGISTER') || Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
|
||||
$this->generateError();
|
||||
}
|
||||
|
||||
protected function generate() : void
|
||||
{
|
||||
$this->title[] = Lang::account('title');
|
||||
|
||||
$msg = $this->activate();
|
||||
|
||||
if ($this->success)
|
||||
$this->inputbox = ['inputbox-status', ['head' => Lang::account('inputbox', 'head', 'register', [2]), 'message' => $msg]];
|
||||
else
|
||||
{
|
||||
$_SESSION['error']['activate'] = $msg;
|
||||
$this->forward('?account=resend');
|
||||
}
|
||||
|
||||
parent::generate();
|
||||
}
|
||||
|
||||
private function activate() : string
|
||||
{
|
||||
if (!$this->assertGET('key'))
|
||||
return Lang::main('intError');
|
||||
|
||||
if (DB::Aowow()->selectCell('SELECT `id` FROM ?_account WHERE `status` IN (?a) AND `token` = ?', [ACC_STATUS_NONE, ACC_STATUS_NEW], $this->_get['key']))
|
||||
{
|
||||
// don't remove the token yet. It's needed on signin page.
|
||||
DB::Aowow()->query('UPDATE ?_account SET `status` = ?d, `statusTimer` = 0, `userGroups` = ?d WHERE `token` = ?', ACC_STATUS_NONE, U_GROUP_NONE, $this->_get['key']);
|
||||
|
||||
// fully apply block for further registration attempts from this ip
|
||||
DB::Aowow()->query('REPLACE INTO ?_account_bannedips (`ip`, `type`, `count`, `unbanDate`) VALUES (?, ?d, ?d + 1, UNIX_TIMESTAMP() + ?d)',
|
||||
User::$ip, IP_BAN_TYPE_REGISTRATION_ATTEMPT, Cfg::get('ACC_FAILED_AUTH_COUNT'), Cfg::get('ACC_FAILED_AUTH_BLOCK'));
|
||||
|
||||
$this->success = true;
|
||||
return Lang::account('inputbox', 'message', 'accActivated', [$this->_get['key']]);
|
||||
}
|
||||
|
||||
// grace period expired and other user claimed name
|
||||
return Lang::main('intError');
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -19,6 +19,7 @@ class AccountSigninResponse extends TemplateResponse
|
||||
use TrGetNext;
|
||||
|
||||
protected string $template = 'text-page-generic';
|
||||
protected string $pageName = 'signin';
|
||||
|
||||
protected array $expectedPOST = array(
|
||||
'username' => ['filter' => FILTER_CALLBACK, 'options' => [Util::class, 'validateLogin'] ],
|
||||
@@ -26,8 +27,8 @@ class AccountSigninResponse extends TemplateResponse
|
||||
'remember_me' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkRememberMe'] ]
|
||||
);
|
||||
protected array $expectedGET = array(
|
||||
'token' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{32}$/']],
|
||||
'next' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FLAG_STRIP_AOWOW ]
|
||||
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{40}$/']],
|
||||
'next' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FLAG_STRIP_AOWOW ]
|
||||
);
|
||||
|
||||
private bool $success = false;
|
||||
@@ -43,46 +44,54 @@ class AccountSigninResponse extends TemplateResponse
|
||||
|
||||
protected function generate() : void
|
||||
{
|
||||
$username = '';
|
||||
$message = '';
|
||||
$username =
|
||||
$error = '';
|
||||
$rememberMe = !!$this->_post['remember_me'];
|
||||
|
||||
$this->title = [Lang::account('title')];
|
||||
|
||||
if ($this->_get['token'])
|
||||
// coming from user recovery or creation, prefill username
|
||||
if ($this->_get['key'])
|
||||
{
|
||||
// coming from username recovery, prefill username
|
||||
if ($_ = DB::Aowow()->selectCell('SELECT `login` FROM ?_account WHERE `status` IN (?a) AND `token` = ? AND `statusTimer` > UNIX_TIMESTAMP()', [ACC_STATUS_RECOVER_USER, ACC_STATUS_OK], $this->_get['token']))
|
||||
$username = $_;
|
||||
if ($userData = DB::Aowow()->selectRow('SELECT a.`login` AS "0", IF(s.`expires`, 0, 1) AS "1" FROM ?_account a LEFT JOIN ?_account_sessions s ON a.`id` = s.`userId` AND a.`token` = s.`sessionId` WHERE a.`status` IN (?a) AND a.`token` = ?',
|
||||
[ACC_STATUS_RECOVER_USER, ACC_STATUS_NONE], $this->_get['key']))
|
||||
[$username, $rememberMe] = $userData;
|
||||
}
|
||||
|
||||
$message = $this->doSignIn();
|
||||
if (!$this->success)
|
||||
User::destroy();
|
||||
else
|
||||
if ($this->doSignIn($error))
|
||||
$this->forward($this->getNext(true));
|
||||
|
||||
if ($error)
|
||||
User::destroy();
|
||||
|
||||
$this->inputbox = ['inputbox-form-signin', array(
|
||||
'head' => Lang::account('inputbox', 'head', 'signin'),
|
||||
'action' => '?account=signin&next='.$this->getNext(),
|
||||
'error' => $message,
|
||||
'error' => $error,
|
||||
'username' => $username,
|
||||
'rememberMe' => !!$this->_post['remember_me'],
|
||||
'rememberMe' => $rememberMe,
|
||||
'hasRecovery' => Cfg::get('ACC_EXT_RECOVER_URL') || Cfg::get('ACC_AUTH_MODE') == AUTH_MODE_SELF,
|
||||
)];
|
||||
|
||||
parent::generate();
|
||||
}
|
||||
|
||||
private function doSignIn() : string
|
||||
private function doSignIn(string &$error) : bool
|
||||
{
|
||||
if (is_null($this->_post['username']) && is_null($this->_post['password']))
|
||||
return '';
|
||||
return false;
|
||||
|
||||
if (!$this->assertPOST('username'))
|
||||
return Lang::account('userNotFound');
|
||||
{
|
||||
$error = Lang::account('userNotFound');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->assertPOST('password'))
|
||||
return Lang::account('wrongPass');
|
||||
{
|
||||
$error = Lang::account('wrongPass');
|
||||
return false;
|
||||
}
|
||||
|
||||
$error = match (User::authenticate($this->_post['username'], $this->_post['password']))
|
||||
{
|
||||
@@ -95,10 +104,7 @@ class AccountSigninResponse extends TemplateResponse
|
||||
default => Lang::main('intError')
|
||||
};
|
||||
|
||||
if (!$error)
|
||||
$this->success = true;
|
||||
|
||||
return $error;
|
||||
return !$error;
|
||||
}
|
||||
|
||||
private function onAuthSuccess() : string
|
||||
@@ -109,14 +115,11 @@ class AccountSigninResponse extends TemplateResponse
|
||||
return Lang::main('intError');
|
||||
}
|
||||
|
||||
$email = filter_var($this->_post['username'], FILTER_VALIDATE_EMAIL);
|
||||
|
||||
// reset account status, update expiration
|
||||
$ok = 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 { `email` = ? } { `login` = ? }',
|
||||
$ok = 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 `id` = ?d',
|
||||
User::$ip, User::$ip, User::$ip,
|
||||
ACC_STATUS_NEW, ACC_STATUS_NEW, ACC_STATUS_NEW,
|
||||
$email ?: DBSIMPLE_SKIP,
|
||||
!$email ? $this->_post['username'] : DBSIMPLE_SKIP
|
||||
User::$id // available after successful User:authenticate
|
||||
);
|
||||
|
||||
if (!is_int($ok)) // num updated fields or null on fail
|
||||
@@ -125,6 +128,10 @@ class AccountSigninResponse extends TemplateResponse
|
||||
return Lang::main('intError');
|
||||
}
|
||||
|
||||
// DELETE temp session
|
||||
if ($this->_get['key'])
|
||||
DB::Aowow()->query('DELETE FROM ?_account_sessions WHERE `sessionId` = ?', $this->_get['key']);
|
||||
|
||||
session_regenerate_id(true); // user status changed => regenerate id
|
||||
|
||||
// create new session entry
|
||||
|
||||
163
endpoints/account/signup.php
Normal file
163
endpoints/account/signup.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Aowow;
|
||||
|
||||
if (!defined('AOWOW_REVISION'))
|
||||
die('illegal access');
|
||||
|
||||
|
||||
/*
|
||||
* accessed via signup link
|
||||
* self referencing
|
||||
*/
|
||||
class AccountSignupResponse extends TemplateResponse
|
||||
{
|
||||
use TrGetNext;
|
||||
|
||||
protected string $template = 'text-page-generic';
|
||||
protected string $pageName = 'signup';
|
||||
|
||||
protected array $expectedPOST = array(
|
||||
'username' => ['filter' => FILTER_SANITIZE_SPECIAL_CHARS, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
|
||||
'email' => ['filter' => FILTER_SANITIZE_EMAIL, 'flags' => FILTER_FLAG_STRIP_AOWOW ],
|
||||
'password' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine'] ],
|
||||
'c_password' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine'] ],
|
||||
'remember_me' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkRememberMe']]
|
||||
);
|
||||
|
||||
protected array $expectedGET = array(
|
||||
'next' => ['filter' => FILTER_SANITIZE_URL, 'flags' => FILTER_FLAG_STRIP_AOWOW]
|
||||
);
|
||||
|
||||
private bool $success = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// if the user is logged in goto account dashboard
|
||||
if (User::isLoggedIn())
|
||||
$this->forward('?account');
|
||||
|
||||
// redirect to external registration page, if set
|
||||
if (Cfg::get('ACC_EXT_CREATE_URL'))
|
||||
$this->forward(Cfg::get('ACC_EXT_CREATE_URL'));
|
||||
|
||||
parent::__construct();
|
||||
|
||||
// registration not enabled on self
|
||||
if (!Cfg::get('ACC_ALLOW_REGISTER'))
|
||||
$this->generateError();
|
||||
|
||||
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
|
||||
$this->generateError();
|
||||
}
|
||||
|
||||
protected function generate() : void
|
||||
{
|
||||
$this->title[] = Lang::account('title');
|
||||
|
||||
// step 1 - no params > signup form
|
||||
// step 2 - any param > status box
|
||||
// step 3 - on ?account=activate
|
||||
|
||||
$message = $this->doSignUp();
|
||||
|
||||
if ($this->success)
|
||||
{
|
||||
$this->inputbox = ['inputbox-status', array(
|
||||
'head' => Lang::account('inputbox', 'head', 'register', [1.5]),
|
||||
'message' => Lang::account('inputbox', 'message', 'createAccSent', [$this->_post['email']])
|
||||
)];
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->inputbox = ['inputbox-form-signup', array(
|
||||
'head' => Lang::account('inputbox', 'head', 'register', [1]),
|
||||
'error' => $message,
|
||||
'action' => '?account=signup&next='.$this->getNext(),
|
||||
'username' => $this->_post['username'] ?? '',
|
||||
'email' => $this->_post['email'] ?? '',
|
||||
'rememberMe' => !!$this->_post['remember_me'],
|
||||
)];
|
||||
}
|
||||
|
||||
parent::generate();
|
||||
}
|
||||
|
||||
private function doSignUp() : string
|
||||
{
|
||||
// no input yet. show clean form
|
||||
if (!$this->assertPOST('username', 'password', 'c_password') && is_null($this->_post['email']))
|
||||
return '';
|
||||
|
||||
// truncated due to validation fail
|
||||
if (!$this->_post['email'])
|
||||
return Lang::account('emailInvalid');
|
||||
|
||||
// check username
|
||||
if (!Util::validateUsername($this->_post['username'], $e))
|
||||
return Lang::account($e == 1 ? 'errNameLength' : 'errNameChars');
|
||||
|
||||
// check password
|
||||
if (!Util::validatePassword($this->_post['password'], $e))
|
||||
return Lang::account($e == 1 ? 'errPassLength' : 'errPassChars');
|
||||
|
||||
if ($this->_post['password'] !== $this->_post['c_password'])
|
||||
return Lang::account('passMismatch');
|
||||
|
||||
// check ip
|
||||
if (!User::$ip)
|
||||
return Lang::main('intError');
|
||||
|
||||
// limit account creation
|
||||
if (DB::Aowow()->selectRow('SELECT 1 FROM ?_account_bannedips WHERE `type` = ?d AND `ip` = ? AND `count` >= ?d AND `unbanDate` >= UNIX_TIMESTAMP()', IP_BAN_TYPE_REGISTRATION_ATTEMPT, User::$ip, Cfg::get('ACC_FAILED_AUTH_COUNT')))
|
||||
{
|
||||
DB::Aowow()->query('UPDATE ?_account_bannedips SET `count` = `count` + 1, `unbanDate` = UNIX_TIMESTAMP() + ?d WHERE `ip` = ? AND `type` = ?d', Cfg::get('ACC_FAILED_AUTH_BLOCK'), User::$ip, IP_BAN_TYPE_REGISTRATION_ATTEMPT);
|
||||
return Lang::account('inputbox', 'error', 'signupExceeded', [Util::formatTime(Cfg::get('ACC_FAILED_AUTH_BLOCK') * 1000)]);
|
||||
}
|
||||
|
||||
// username / email taken
|
||||
if ($inUseData = DB::Aowow()->SelectRow('SELECT `id`, `username`, `status` = ?d AND `statusTimer` < UNIX_TIMESTAMP() AS "expired" FROM ?_account WHERE (LOWER(`username`) = LOWER(?) OR LOWER(`email`) = LOWER(?))', ACC_STATUS_NEW, $this->_post['username'], $this->_post['email']))
|
||||
{
|
||||
if ($inUseData['expired'])
|
||||
DB::Aowow()->query('DELETE FROM ?_account WHERE `id` = ?d', $inUseData['id']);
|
||||
else
|
||||
return Util::lower($inUseData['username']) == Util::lower($this->_post['username']) ? Lang::account('nameInUse') : Lang::account('mailInUse');
|
||||
}
|
||||
|
||||
// create..
|
||||
$token = Util::createHash();
|
||||
$userId = DB::Aowow()->query('INSERT INTO ?_account (`login`, `passHash`, `username`, `email`, `joindate`, `curIP`, `locale`, `userGroups`, `status`, `statusTimer`, `token`) VALUES (?, ?, ?, ?, UNIX_TIMESTAMP(), ?, ?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 (!$userId)
|
||||
return Lang::main('intError');
|
||||
|
||||
// create session tied to the token to store remember_me status
|
||||
DB::Aowow()->query('INSERT INTO ?_account_sessions (`userId`, `sessionId`, `created`, `expires`, `touched`, `deviceInfo`, `ip`, `status`) VALUES (?d, ?, ?d, ?d, ?d, ?, ?, ?d)',
|
||||
$userId, $token, time(), $this->_post['remember_me'] ? 0 : time() + Cfg::get('SESSION_TIMEOUT_DELAY'), time(), User::$agent, User::$ip, SESSION_ACTIVE);
|
||||
|
||||
if (!Util::sendMail($this->_post['email'], 'activate-account', [$token], Cfg::get('ACC_CREATE_SAVE_DECAY')))
|
||||
return Lang::main('intError2', ['send mail']);
|
||||
|
||||
// success: update ip-bans
|
||||
DB::Aowow()->query('INSERT INTO ?_account_bannedips (`ip`, `type`, `count`, `unbanDate`) VALUES (?, ?d, 1, UNIX_TIMESTAMP() + ?d) ON DUPLICATE KEY UPDATE `count` = `count` + 1, `unbanDate` = UNIX_TIMESTAMP() + ?d',
|
||||
User::$ip, IP_BAN_TYPE_REGISTRATION_ATTEMPT, Cfg::get('ACC_FAILED_AUTH_BLOCK'), Cfg::get('ACC_FAILED_AUTH_BLOCK'));
|
||||
|
||||
Util::gainSiteReputation($userId, SITEREP_ACTION_REGISTER);
|
||||
|
||||
$this->success = true;
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1,23 +1,10 @@
|
||||
<?php namespace Aowow; ?>
|
||||
|
||||
<?php $this->brick('header'); ?>
|
||||
|
||||
<div class="main" id="main">
|
||||
<div class="main-precontents" id="main-precontents"></div>
|
||||
<div class="main-contents" id="main-contents">
|
||||
<?php
|
||||
$this->brick('announcement');
|
||||
namespace Aowow\Template;
|
||||
|
||||
$this->brick('pageTemplate');
|
||||
use \Aowow\Lang;
|
||||
?>
|
||||
<div class="pad3"></div>
|
||||
<?php if (!empty($this->text)): ?>
|
||||
<div class="inputbox">
|
||||
<h1><?=$this->head; ?></h1>
|
||||
<div id="inputbox-error"></div>
|
||||
<div style="text-align: center; font-size: 110%"><?=$this->text; ?></div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
|
||||
<script type="text/javascript">
|
||||
function inputBoxValidate(f)
|
||||
{
|
||||
@@ -77,20 +64,18 @@
|
||||
e.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<form action="?account=signup&next=<?=$this->next; ?>" method="post" onsubmit="return inputBoxValidate(this)">
|
||||
<form action="<?=$action ?? '.'; ?>" method="post" onsubmit="return inputBoxValidate(this)">
|
||||
<div class="inputbox" style="position: relative">
|
||||
<h1><?=$this->head; ?></h1>
|
||||
<div id="inputbox-error"><?=$this->error; ?></div>
|
||||
<h1><?=$head ?? ''; ?></h1>
|
||||
<div id="inputbox-error"><?=$error ?? ''; ?></div>
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="right"><?=Lang::account('user').Lang::main('colon'); ?></td>
|
||||
<td><input type="text" name="username" value="" maxlength="16" id="username-generic" style="width: 10em" /></td>
|
||||
<td><input type="text" name="username" value="<?=$username ?? ''; ?>" maxlength="16" id="username-generic" style="width: 10em" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right"><?=Lang::account('pass').Lang::main('colon'); ?></td>
|
||||
@@ -103,9 +88,9 @@
|
||||
<tr>
|
||||
<tr>
|
||||
<td align="right"><?=Lang::account('email').Lang::main('colon'); ?></td>
|
||||
<td><input type="text" name="email" style="width: 10em" /></td>
|
||||
<td><input type="text" name="email" value="<?=$email ?? ''; ?>" style="width: 10em" /></td>
|
||||
</tr>
|
||||
<td align="right" valign="top"><input type="checkbox" name="remember_me" id="remember_me" value="yes" /></td>
|
||||
<td align="right" valign="top"><input type="checkbox" name="remember_me" id="remember_me" value="yes"<?=($rememberMe ?? '' ? ' checked="checked"' : ''); ?> /></td>
|
||||
<td>
|
||||
<label for="remember_me"><?=Lang::account('rememberMe'); ?></label>
|
||||
<div class="pad2"></div>
|
||||
@@ -118,9 +103,3 @@
|
||||
</form>
|
||||
|
||||
<script type="text/javascript">$WH.ge('username-generic').focus()</script>
|
||||
<?php endif; ?>
|
||||
<div class="clear"></div>
|
||||
</div><!-- main-contents -->
|
||||
</div><!-- main -->
|
||||
|
||||
<?php $this->brick('footer'); ?>
|
||||
Reference in New Issue
Block a user