This commit is contained in:
Marcin Kurczewski 2013-10-16 18:07:23 +02:00
parent 52956b56c8
commit 39db2a64e1
14 changed files with 188 additions and 172 deletions

View file

@ -19,14 +19,16 @@ endlessScrolling=1
maxSearchTokens=4 maxSearchTokens=4
[registration] [registration]
emailActivation = 0 staffActivation = 1
staffActivation = 0
passMinLength = 5 passMinLength = 5
passRegex = "/^.+$/" passRegex = "/^.+$/"
userNameMinLength = 3 userNameMinLength = 3
userNameRegex = "/^[\w_-]+$/ui" userNameRegex = "/^[\w_-]+$/ui"
salt = "1A2/$_4xVa" salt = "1A2/$_4xVa"
needEmailForRegistering = 1
needEmailForCommenting = 0
needEmailForUploading = 1
activationEmailSenderName = "{host} registration engine" activationEmailSenderName = "{host} registration engine"
activationEmailSenderEmail = "noreply@{host}" activationEmailSenderEmail = "noreply@{host}"
activationEmailSubject = "{host} activation" activationEmailSubject = "{host} activation"

View file

@ -1,8 +1,10 @@
img { .user img {
width: 100px;
height: 100px;
float: left; float: left;
margin-right: 0.5em; margin-right: 0.5em;
} }
h1 { .user h1 {
margin-top: 0; margin-top: 0;
margin-bottom: 0.25em; margin-bottom: 0.25em;
} }

View file

@ -17,7 +17,7 @@ function scrolled()
var dom = $(response); var dom = $(response);
var nextPage = dom.find('.paginator .next:not(.disabled) a').attr('href'); var nextPage = dom.find('.paginator .next:not(.disabled) a').attr('href');
$(document).data('page-next', nextPage); $(document).data('page-next', nextPage);
$('.paginator-content').append($(response).find('.paginator-content').children().fadeIn('slow')); $('.paginator-content').append($(response).find('.paginator-content').children().css({opacity: 0}).animate({opacity: 1}, 'slow'));
scrolled(); scrolled();
}); });
} }

View file

@ -35,8 +35,8 @@ class AuthController
if ($dbUser->banned) if ($dbUser->banned)
throw new SimpleException('You are banned'); throw new SimpleException('You are banned');
if (!$dbUser->email_confirmed and $this->config->registration->emailActivation) if ($this->config->registration->needEmailForRegistering)
throw new SimpleException('You haven\'t confirmed your e-mail address yet'); PrivilegesHelper::confirmEmail($dbUser);
$_SESSION['user-id'] = $dbUser->id; $_SESSION['user-id'] = $dbUser->id;
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index')); \Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
@ -54,144 +54,4 @@ class AuthController
unset($_SESSION['user-id']); unset($_SESSION['user-id']);
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index')); \Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
} }
/**
* @route /register
*/
public function registerAction()
{
$this->context->handleExceptions = true;
$this->context->stylesheets []= 'auth.css';
$this->context->subTitle = 'registration form';
//check if already logged in
if ($this->context->loggedIn)
{
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
return;
}
$suppliedName = InputHelper::get('name');
$suppliedPassword1 = InputHelper::get('password1');
$suppliedPassword2 = InputHelper::get('password2');
$suppliedEmail = InputHelper::get('email');
$this->context->suppliedName = $suppliedName;
$this->context->suppliedPassword1 = $suppliedPassword1;
$this->context->suppliedPassword2 = $suppliedPassword2;
$this->context->suppliedEmail = $suppliedEmail;
$regConfig = $this->config->registration;
$emailActivation = $regConfig->emailActivation;
$staffActivation = $regConfig->staffActivation;
$this->context->transport->staffActivation = $staffActivation;
$this->context->transport->emailActivation = $emailActivation;
if ($suppliedName !== null)
{
$suppliedName = Model_User::validateUserName($suppliedName);
if ($suppliedPassword1 != $suppliedPassword2)
throw new SimpleException('Specified passwords must be the same');
$suppliedPassword = Model_User::validatePassword($suppliedPassword1);
$suppliedEmail = Model_User::validateEmail($suppliedEmail);
if (empty($suppliedEmail) and $emailActivation)
throw new SimpleException('E-mail address is required - you will be sent confirmation e-mail.');
//register the user
$dbUser = R::dispense('user');
$dbUser->name = $suppliedName;
$dbUser->pass_salt = md5(mt_rand() . uniqid());
$dbUser->pass_hash = Model_User::hashPassword($suppliedPassword, $dbUser->pass_salt);
$dbUser->email = $suppliedEmail;
$dbUser->join_date = time();
if (R::findOne('user') === null)
{
$dbUser->staff_confirmed = true;
$dbUser->email_confirmed = true;
$dbUser->access_rank = AccessRank::Admin;
}
else
{
$dbUser->staff_confirmed = false;
$dbUser->email_confirmed = false;
$dbUser->access_rank = AccessRank::Registered;
}
//prepare unique registration token
do
{
$emailToken = md5(mt_rand() . uniqid());
}
while (R::findOne('user', 'email_token = ?', [$emailToken]) !== null);
$dbUser->email_token = $emailToken;
//send the e-mail
if ($emailActivation)
{
$tokens = [];
$tokens['host'] = $_SERVER['HTTP_HOST'];
$tokens['link'] = \Chibi\UrlHelper::route('auth', 'activation', ['token' => $dbUser->email_token]);
$body = wordwrap(TextHelper::replaceTokens($regConfig->activationEmailBody, $tokens), 70);
$subject = TextHelper::replaceTokens($regConfig->activationEmailSubject, $tokens);
$senderName = TextHelper::replaceTokens($regConfig->activationEmailSenderName, $tokens);
$senderEmail = $regConfig->activationEmailSenderEmail;
$headers = [];
$headers[] = sprintf('From: %s <%s>', $senderName, $senderEmail);
$headers[] = sprintf('Subject: %s', $subject);
$headers[] = sprintf('X-Mailer: PHP/%s', phpversion());
mail($dbUser->email, $subject, $body, implode("\r\n", $headers));
}
//save the user to db if everything went okay
R::store($dbUser);
$this->context->transport->success = true;
if (!$emailActivation and !$staffActivation)
{
$_SESSION['user-id'] = $dbUser->id;
\Chibi\Registry::getBootstrap()->attachUser();
}
}
}
/**
* @route /activation/{token}
*/
public function activationAction($token)
{
$this->context->subTitle = 'account activation';
//check if already logged in
if ($this->context->loggedIn)
{
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
return;
}
if (empty($token))
throw new SimpleException('Invalid activation token');
$dbUser = R::findOne('user', 'email_token = ?', [$token]);
if ($dbUser === null)
throw new SimpleException('No user with such activation token');
if ($dbUser->email_confirmed)
throw new SimpleException('This user was already activated');
$dbUser->email_confirmed = true;
R::store($dbUser);
$this->context->transport->success = true;
$staffActivation = $this->config->registration->staffActivation;
$this->context->transport->staffActivation = $staffActivation;
if (!$staffActivation)
{
$_SESSION['user-id'] = $dbUser->id;
\Chibi\Registry::getBootstrap()->attachUser();
}
}
} }

View file

@ -175,6 +175,8 @@ class PostController
$this->context->scripts []= 'upload.js'; $this->context->scripts []= 'upload.js';
$this->context->subTitle = 'upload'; $this->context->subTitle = 'upload';
PrivilegesHelper::confirmWithException($this->context->user, Privilege::UploadPost); PrivilegesHelper::confirmWithException($this->context->user, Privilege::UploadPost);
if ($this->config->registration->needEmailForUploading)
PrivilegesHelper::confirmEmail($this->context->user);
if (!empty($_FILES['file']['name'])) if (!empty($_FILES['file']['name']))
{ {

View file

@ -9,6 +9,27 @@ class UserController
return $user; return $user;
} }
private static function sendEmailConfirmation($user)
{
\Chibi\Registry::getContext()->mailSent = true;
$regConfig = \Chibi\Registry::getConfig()->registration;
$tokens = [];
$tokens['host'] = $_SERVER['HTTP_HOST'];
$tokens['link'] = \Chibi\UrlHelper::route('user', 'activation', ['token' => $user->email_token]);
$body = wordwrap(TextHelper::replaceTokens($regConfig->activationEmailBody, $tokens), 70);
$subject = TextHelper::replaceTokens($regConfig->activationEmailSubject, $tokens);
$senderName = TextHelper::replaceTokens($regConfig->activationEmailSenderName, $tokens);
$senderEmail = $regConfig->activationEmailSenderEmail;
$recipientEmail = $user->email_unconfirmed;
$headers = [];
$headers[] = sprintf('From: %s <%s>', $senderName, $senderEmail);
$headers[] = sprintf('Subject: %s', $subject);
$headers[] = sprintf('X-Mailer: PHP/%s', phpversion());
mail($recipientEmail, $subject, $body, implode("\r\n", $headers));
}
/** /**
@ -55,7 +76,8 @@ class UserController
$dbQuery->orderBy('join_date')->desc(); $dbQuery->orderBy('join_date')->desc();
break; break;
case 'pending': case 'pending':
$dbQuery->whereNot('staff_confirmed')->and($this->config->registration->staffActivation); $dbQuery->where('staff_confirmed IS NULL');
$dbQuery->or('staff_confirmed = 0');
break; break;
default: default:
throw new SimpleException('Unknown sort style'); throw new SimpleException('Unknown sort style');
@ -218,11 +240,20 @@ class UserController
$edited = true; $edited = true;
} }
if ($suppliedEmail != '' and $suppliedEmail != $user->email) if ($suppliedEmail != '' and $suppliedEmail != $user->email_confirmed)
{ {
PrivilegesHelper::confirmWithException($this->context->user, Privilege::ChangeUserEmail, $secondary); PrivilegesHelper::confirmWithException($this->context->user, Privilege::ChangeUserEmail, $secondary);
$suppliedEmail = Model_User::validateEmail($suppliedEmail); $suppliedEmail = Model_User::validateEmail($suppliedEmail);
$user->email = $suppliedEmail; if ($this->context->user->id == $user->id)
{
$user->email_unconfirmed = $suppliedEmail;
if (!empty($user->email_unconfirmed))
self::sendEmailConfirmation($user);
}
else
{
$user->email_confirmed = $suppliedEmail;
}
$edited = true; $edited = true;
} }
@ -370,4 +401,117 @@ class UserController
$this->context->transport->success = true; $this->context->transport->success = true;
} }
/**
* @route /register
*/
public function registrationAction()
{
$this->context->handleExceptions = true;
$this->context->stylesheets []= 'auth.css';
$this->context->subTitle = 'registration form';
//check if already logged in
if ($this->context->loggedIn)
{
\Chibi\UrlHelper::forward(\Chibi\UrlHelper::route('index', 'index'));
return;
}
$suppliedName = InputHelper::get('name');
$suppliedPassword1 = InputHelper::get('password1');
$suppliedPassword2 = InputHelper::get('password2');
$suppliedEmail = InputHelper::get('email');
$this->context->suppliedName = $suppliedName;
$this->context->suppliedPassword1 = $suppliedPassword1;
$this->context->suppliedPassword2 = $suppliedPassword2;
$this->context->suppliedEmail = $suppliedEmail;
if ($suppliedName !== null)
{
$suppliedName = Model_User::validateUserName($suppliedName);
if ($suppliedPassword1 != $suppliedPassword2)
throw new SimpleException('Specified passwords must be the same');
$suppliedPassword = Model_User::validatePassword($suppliedPassword1);
$suppliedEmail = Model_User::validateEmail($suppliedEmail);
if (empty($suppliedEmail) and $this->config->registration->needEmailForRegistering)
throw new SimpleException('E-mail address is required - you will be sent confirmation e-mail.');
//register the user
$dbUser = R::dispense('user');
$dbUser->name = $suppliedName;
$dbUser->pass_salt = md5(mt_rand() . uniqid());
$dbUser->pass_hash = Model_User::hashPassword($suppliedPassword, $dbUser->pass_salt);
$dbUser->email_unconfirmed = $suppliedEmail;
//prepare unique registration token
do
{
$emailToken = md5(mt_rand() . uniqid());
}
while (R::findOne('user', 'email_token = ?', [$emailToken]) !== null);
$dbUser->email_token = $emailToken;
$dbUser->join_date = time();
if (R::findOne('user') === null)
{
$dbUser->access_rank = AccessRank::Admin;
$dbUser->staff_confirmed = true;
$dbUser->email_confirmed = $suppliedEmail;
}
else
{
$dbUser->access_rank = AccessRank::Registered;
$dbUser->staff_confirmed = false;
$dbUser->staff_confirmed = null;
if (!empty($dbUser->email_unconfirmed))
self::sendEmailConfirmation($dbUser);
}
//save the user to db if everything went okay
R::store($dbUser);
$this->context->transport->success = true;
if (!$this->config->registration->needEmailForRegistering and !$this->config->registration->staffActivation)
{
$_SESSION['user-id'] = $dbUser->id;
\Chibi\Registry::getBootstrap()->attachUser();
}
}
}
/**
* @route /activation/{token}
*/
public function activationAction($token)
{
$this->context->subTitle = 'account activation';
if (empty($token))
throw new SimpleException('Invalid activation token');
$dbUser = R::findOne('user', 'email_token = ?', [$token]);
if ($dbUser === null)
throw new SimpleException('No user with such activation token');
if (!$dbUser->email_unconfirmed)
throw new SimpleException('This user was already activated');
$dbUser->email_confirmed = $dbUser->email_unconfirmed;
$dbUser->email_unconfirmed = null;
R::store($dbUser);
$this->context->transport->success = true;
if (!$this->config->registration->staffActivation)
{
$_SESSION['user-id'] = $dbUser->id;
\Chibi\Registry::getBootstrap()->attachUser();
}
}
} }

View file

@ -48,6 +48,12 @@ class PrivilegesHelper
throw new SimpleException('Insufficient privileges'); throw new SimpleException('Insufficient privileges');
} }
} }
public static function confirmEmail($user)
{
if (!$user->email_confirmed)
throw new SimpleException('Need e-mail address confirmation to continue');
}
} }
PrivilegesHelper::init(); PrivilegesHelper::init();

View file

@ -3,8 +3,8 @@ class Model_User extends RedBean_SimpleModel
{ {
public function getAvatarUrl($size = 32) public function getAvatarUrl($size = 32)
{ {
$subject = !empty($this->email) $subject = !empty($this->email_confirmed)
? $this->email ? $this->email_confirmed
: $this->pass_salt . $this->name; : $this->pass_salt . $this->name;
$hash = md5(strtolower(trim($subject))); $hash = md5(strtolower(trim($subject)));
$url = 'http://www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro'; $url = 'http://www.gravatar.com/avatar/' . $hash . '?s=' . $size . '&d=retro';
@ -59,7 +59,7 @@ class Model_User extends RedBean_SimpleModel
$dbUser = R::findOne('user', 'name = ?', [$userName]); $dbUser = R::findOne('user', 'name = ?', [$userName]);
if ($dbUser !== null) if ($dbUser !== null)
{ {
if (!$dbUser->email_confirmed and \Chibi\Registry::getConfig()->registration->emailActivation) if (!$dbUser->email_confirmed and \Chibi\Registry::getConfig()->registration->needEmailForRegistering)
throw new SimpleException('User with this name is already registered and awaits e-mail confirmation'); throw new SimpleException('User with this name is already registered and awaits e-mail confirmation');
if (!$dbUser->staff_confirmed and \Chibi\Registry::getConfig()->registration->staffActivation) if (!$dbUser->staff_confirmed and \Chibi\Registry::getConfig()->registration->staffActivation)

View file

@ -1,6 +0,0 @@
<?php if ($this->context->transport->success === true): ?>
<p>Activation completed successfully.</p>
<?php if ($this->context->transport->staffActivation): ?>
<p>However, your account still must be confirmed by staff.</p>
<?php endif ?>
<?php endif ?>

View file

@ -1,7 +1,7 @@
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'login') ?>" class="auth aligned" method="post"> <form action="<?php echo \Chibi\UrlHelper::route('auth', 'login') ?>" class="auth aligned" method="post">
<div> <div>
<p>If you don't have an account yet,<br/><a href="<?php echo \Chibi\UrlHelper::route('auth', 'register'); ?>">click here</a> to create a new one.</p> <p>If you don't have an account yet,<br/><a href="<?php echo \Chibi\UrlHelper::route('user', 'registration'); ?>">click here</a> to create a new one.</p>
</div> </div>
<div> <div>

View file

@ -44,7 +44,7 @@
if (!$this->context->loggedIn) if (!$this->context->loggedIn)
{ {
$nav []= ['Log in', \Chibi\UrlHelper::route('auth', 'login')]; $nav []= ['Log in', \Chibi\UrlHelper::route('auth', 'login')];
$nav []= ['Register', \Chibi\UrlHelper::route('auth', 'register')]; $nav []= ['Register', \Chibi\UrlHelper::route('user', 'registration')];
} }
else else
{ {

View file

@ -0,0 +1,6 @@
<?php if ($this->context->transport->success === true): ?>
<p class="alert alert-success">Activation completed successfully.
<?php if ($this->config->registration->staffActivation): ?>
<br>However, your account still must be confirmed by staff.
<?php endif ?></p>
<?php endif ?>

View file

@ -1,13 +1,13 @@
<?php if ($this->context->transport->success === true): ?> <?php if ($this->context->transport->success === true): ?>
<p>Congratulations, your account was created.</p> <p class="alert alert-success">Congratulations, your account was created.
<?php if ($this->context->transport->emailActivation): ?> <?php if (!empty($this->context->mailSent)): ?>
<p>Please wait for activation e-mail.</p> <br>Please wait for activation e-mail.
<?php if ($this->context->transport->staffActivation): ?> <?php if ($this->config->registration->staffActivation): ?>
<p>After this, your registration must be confirmed by staff.</p> <br>After this, your registration must be confirmed by staff.
<?php endif ?>
<?php elseif ($this->context->transport->staffActivation): ?>
<p>Your registration must be confirmed by staff.</p>
<?php endif ?> <?php endif ?>
<?php elseif ($this->config->registration->staffActivation): ?>
<br>Your registration must be now confirmed by staff.
<?php endif ?></p>
<?php else: ?> <?php else: ?>
<form action="<?php echo \Chibi\UrlHelper::route('auth', 'register') ?>" class="auth aligned" method="post"> <form action="<?php echo \Chibi\UrlHelper::route('auth', 'register') ?>" class="auth aligned" method="post">
<div> <div>
@ -30,7 +30,7 @@
</div> </div>
<div> <div>
<label class="left" for="email">E-mail address<?php if ($this->context->transport->emailActivation) echo ' (required)' ?>:</label> <label class="left" for="email">E-mail address:</label>
<input id="email" name="email" value="<?php echo $this->context->suppliedEmail ?>" placeholder="e.g. vader@empire.gov" autocomplete="off"/> <input id="email" name="email" value="<?php echo $this->context->suppliedEmail ?>" placeholder="e.g. vader@empire.gov" autocomplete="off"/>
</div> </div>

View file

@ -22,7 +22,7 @@
<?php if ($this->context->user->id == $this->context->transport->user->id): ?> <?php if ($this->context->user->id == $this->context->transport->user->id): ?>
<div class="key-value email"> <div class="key-value email">
<span class="key">E-mail:</span> <span class="key">E-mail:</span>
<span class="value" title="<?php echo $val = ($this->context->transport->user->email ?: 'none specified') ?>"><?php echo $val ?></span> <span class="value" title="<?php echo $val = ($this->context->transport->user->email_unconfirmed ? '(unconfirmed) ' . $this->context->transport->user->email_unconfirmed : $this->context->transport->user->email_confirmed ?: 'none specified') ?>"><?php echo $val ?></span>
<br>(only you can see this) <br>(only you can see this)
</div> </div>
<?php endif ?> <?php endif ?>
@ -199,7 +199,7 @@
<?php endif ?> <?php endif ?>
<?php if ($this->context->transport->success === true): ?> <?php if ($this->context->transport->success === true): ?>
<p class="alert alert-success">Account settings updated!</p> <p class="alert alert-success">Account settings updated! <?php if (!empty($this->context->mailSent)) echo 'You will be sent new e-mail address confirmation message soon.' ?></p>
<?php elseif (isset($this->context->transport->errorMessage)): ?> <?php elseif (isset($this->context->transport->errorMessage)): ?>
<p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p> <p class="alert alert-error">Error: <?php echo $this->context->transport->errorMessage ?></p>
<?php endif ?> <?php endif ?>