User registration procedure

This commit is contained in:
Sergio Brighenti 2020-02-27 15:18:01 +01:00
parent 3d8fee6f61
commit 0269eaa6f6
14 changed files with 247 additions and 89 deletions

View file

@ -37,6 +37,7 @@ class AdminController extends Controller
$registerEnabled = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'register_enabled\'')->fetch()->value ?? 'off';
$hideByDefault = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'hide_by_default\'')->fetch()->value ?? 'off';
$copyUrl = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'copy_url_behavior\'')->fetch()->value ?? 'off';
$quotaEnabled = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'quota_enabled\'')->fetch()->value ?? 'off';
$defaultUserQuota = $this->database->query('SELECT `value` FROM `settings` WHERE `key` = \'default_user_quota\'')->fetch()->value ?? '1G';
return view()->render($response, 'dashboard/system.twig', [
@ -53,7 +54,8 @@ class AdminController extends Controller
'register_enabled' => $registerEnabled,
'hide_by_default' => $hideByDefault,
'copy_url_behavior' => $copyUrl,
'default_user_quota' => $defaultUserQuota,
'quota_enabled' => $quotaEnabled,
'default_user_quota' => humanFileSize($defaultUserQuota, 0, true),
]);
}

View file

@ -4,6 +4,7 @@
namespace App\Controllers\Auth;
use App\Controllers\Controller;
use App\Web\Mail;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Exception\HttpNotFoundException;
@ -100,6 +101,18 @@ class RegisterController extends Controller
$activateToken,
]);
Mail::make()
->from('no-reply@'.str_ireplace('www.', '', parse_url($this->config['base_url'], PHP_URL_HOST)), $this->config['app_name'])
->to(param($request, 'email'))
->subject(lang('mail.activate_account', [$this->config['app_name']]))
->message(lang('mail.activate_text', [
param($request, 'username'),
$this->config['app_name'],
$this->config['base_url'],
route('activate', ['activateToken' => $activateToken]),
]))
->send();
$this->session->alert(lang('register_success', [param($request, 'username')]), 'success');
$this->logger->info('New user registered.', [array_diff_key($request->getParsedBody(), array_flip(['password']))]);
@ -114,5 +127,24 @@ class RegisterController extends Controller
*/
public function activateUser(Request $request, Response $response, string $activateToken): Response
{
if ($this->session->get('logged', false)) {
return redirect($response, route('home'));
}
$userId = $this->database->query('SELECT `id` FROM `users` WHERE `activate_token` = ? LIMIT 1', $activateToken)->fetch()->id ?? null;
if ($userId === null) {
$this->session->alert(lang('account_not_found'), 'warning');
return redirect($response, route('login.show'));
}
$this->database->query('UPDATE `users` SET `activate_token`=?, `active`=? WHERE `id` = ?', [
null,
1,
$userId,
]);
$this->session->alert(lang('account_activated'), 'success');
return redirect($response, route('login.show'));
}
}

View file

@ -23,7 +23,8 @@ class SettingController extends Controller
$this->updateSetting('register_enabled', param($request, 'register_enabled', 'off'));
$this->updateSetting('hide_by_default', param($request, 'hide_by_default', 'off'));
$this->updateSetting('default_user_quota', param($request, 'default_user_quota', '1G'));
$this->updateSetting('quota_enabled', param($request, 'quota_enabled', 'off'));
$this->updateSetting('default_user_quota', stringToBytes(param($request, 'default_user_quota', '1G')));
$this->updateSetting('copy_url_behavior', param($request, 'copy_url_behavior') === null ? 'default' : 'raw');
$this->applyTheme($request);

127
app/Web/Mail.php Normal file
View file

@ -0,0 +1,127 @@
<?php
namespace App\Web;
use InvalidArgumentException;
class Mail
{
protected $fromMail = 'no-reply@example.com';
protected $fromName;
protected $to;
protected $subject;
protected $message;
protected $additionalHeaders = '';
protected $headers = '';
/**
* @return Mail
*/
public static function make()
{
return new self();
}
/**
* @param $mail
* @param $name
* @return $this
*/
public function from(string $mail, string $name)
{
$this->fromMail = $mail;
$this->fromName = $name;
return $this;
}
/**
* @param $mail
* @return $this
*/
public function to(string $mail)
{
if (!filter_var($mail, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Mail not valid.');
}
$this->to = $mail;
return $this;
}
/**
* @param $text
* @return $this
*/
public function subject(string $text)
{
$this->subject = htmlentities($text);
return $this;
}
/**
* @param $text
* @return $this
*/
public function message(string $text)
{
$this->message = htmlentities($text);
return $this;
}
/**
* @param $header
* @return $this
*/
public function addHeader(string $header)
{
$this->additionalHeaders .= "$header\r\n";
return $this;
}
/**
* @param $header
* @return $this
*/
protected function addRequiredHeader(string $header)
{
$this->headers .= "$header\r\n";
return $this;
}
/**
* @return int
*/
public function send()
{
if ($this->to === null) {
throw new InvalidArgumentException('Target email cannot be null.');
}
if ($this->subject === null) {
throw new InvalidArgumentException('Subject cannot be null.');
}
if ($this->message === null) {
throw new InvalidArgumentException('Message cannot be null.');
}
if ($this->fromName === null) {
$this->addRequiredHeader("From: $this->fromMail");
} else {
$this->addRequiredHeader("From: $this->fromName <$this->fromMail>");
}
$this->addRequiredHeader('X-Mailer: PHP/'.phpversion());
$this->addRequiredHeader('MIME-Version: 1.0');
$this->addRequiredHeader('Content-Type: text/html; charset=iso-8859-1');
$this->headers .= $this->additionalHeaders;
return (int) mail($this->to, $this->subject, $this->message, $this->headers);
}
}

View file

@ -15,13 +15,18 @@ if (!function_exists('humanFileSize')) {
* @param $size
* @param int $precision
*
* @param bool $iniMode
* @return string
*/
function humanFileSize($size, $precision = 2): string
function humanFileSize($size, $precision = 2, $iniMode = false): string
{
for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {
}
if ($iniMode) {
return round($size, $precision).['B', 'K', 'M', 'G', 'T'][$i];
}
return round($size, $precision).' '.['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i];
}
}
@ -410,7 +415,7 @@ if (!function_exists('inPath')) {
{
$path = parse_url(urlFor($path), PHP_URL_PATH);
return substr($uri, 0, strlen($uri)) === $path;
return substr($uri, 0, strlen($path)) === $path;
}
}

View file

@ -65,6 +65,7 @@ $app->group('', function (RouteCollectorProxy $group) {
$app->get('/', [DashboardController::class, 'redirects'])->setName('root');
$app->get('/register', [RegisterController::class, 'registerForm'])->setName('register.show');
$app->post('/register', [RegisterController::class, 'register'])->setName('register');
$app->get('/activate/{activateToken}', [RegisterController::class, 'activateUser'])->setName('activate');
$app->get('/login', [LoginController::class, 'show'])->setName('login.show');
$app->post('/login', [LoginController::class, 'login'])->setName('login');
$app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout');

View file

@ -121,4 +121,5 @@ return [
'register' => 'Register',
'default_user_quota' => 'Default User Quota',
'invalid_quota' => 'Invalid values as default user quota.',
'mail.activate_text' => "Hi %s!\nthank you for creating your account on %s (%s), click on the following link to activate it:\n\n%s"
];

View file

@ -1,7 +1,8 @@
ALTER TABLE `users`
ADD COLUMN `activate_token` VARCHAR(32) DEFAULT NULL,
ADD COLUMN `reset_token` VARCHAR(32) DEFAULT NULL,
ADD COLUMN `disk_quota` BIGINT(20) NOT NULL DEFAULT -1;
ADD COLUMN `current_disk_quota` BIGINT(20) NOT NULL DEFAULT 0,
ADD COLUMN `max_disk_quota` BIGINT(20) NOT NULL DEFAULT -1;
ALTER TABLE `users` ADD INDEX (`activate_token`);
ALTER TABLE `users` ADD INDEX (`reset_token`);

View file

@ -1,6 +1,7 @@
ALTER TABLE `users` ADD COLUMN `activate_token` VARCHAR(32);
ALTER TABLE `users` ADD COLUMN `reset_token` VARCHAR(32);
ALTER TABLE `users` ADD COLUMN `disk_quota` BIGINT NOT NULL DEFAULT -1;
ALTER TABLE `users` ADD COLUMN `current_disk_quota` BIGINT NOT NULL DEFAULT 0;
ALTER TABLE `users` ADD COLUMN `max_disk_quota` BIGINT NOT NULL DEFAULT -1;
CREATE INDEX IF NOT EXISTS `activate_token_index`
ON `users` (`activate_token`);

View file

@ -38,7 +38,7 @@
<label for="username" class="sr-only">{{ lang('username') }}</label>
<input type="text" id="username" class="form-control" placeholder="{{ lang('username') }}" name="username" required autofocus>
<label for="email" class="sr-only">E-Mail</label>
<input type="email" id="email" class="form-control" placeholder="mail@example.com" name="password" required>
<input type="email" id="email" class="form-control" placeholder="mail@example.com" name="email" required>
<label for="password" class="sr-only">{{ lang('password') }}</label>
<input type="password" id="password" class="form-control" placeholder="{{ lang('password') }}" name="password" required>
</div>

View file

@ -18,7 +18,7 @@
</li>
{% if session.get('admin') %}
<li class="nav-item">
<a href="{{ route('user.index') }}" class="nav-link {{ inPath(request.uri.path, '/users') ? 'active' }}"><i class="fas fa-fw fa-users"></i>
<a href="{{ route('user.index') }}" class="nav-link {{ inPath(request.uri.path, '/user') ? 'active' }}"><i class="fas fa-fw fa-users"></i>
{{ lang('users') }}
</a>
</li>

View file

@ -61,28 +61,28 @@
<form method="post" action="{{ route('settings.save') }}">
<div class="form-group row">
<label for="custom_head" class="col-sm-4 col-form-label">{{ lang('register_enabled') }}</label>
<label for="register_enabled" class="col-sm-4 col-form-label">{{ lang('register_enabled') }}</label>
<div class="col-sm-8">
<input type="checkbox" name="register_enabled" data-toggle="toggle" {{ register_enabled == 'on' ? 'checked' }}>
</div>
</div>
<div class="form-group row">
<label for="custom_head" class="col-sm-4 col-form-label">{{ lang('hide_by_default') }}</label>
<label for="hide_by_default" class="col-sm-4 col-form-label">{{ lang('hide_by_default') }}</label>
<div class="col-sm-8">
<input type="checkbox" name="hide_by_default" data-toggle="toggle" {{ hide_by_default == 'on' ? 'checked' }}>
</div>
</div>
<div class="form-group row">
<label for="custom_head" class="col-sm-4 col-form-label">{{ lang('copy_url_behavior') }}</label>
<label for="copy_url_behavior" class="col-sm-4 col-form-label">{{ lang('copy_url_behavior') }}</label>
<div class="col-sm-8">
<input type="checkbox" name="copy_url_behavior" data-toggle="toggle" data-off="Default URL" data-on="Raw URL" data-onstyle="primary" data-offstyle="secondary" {{ copy_url_behavior == 'raw' ? 'checked' }}>
</div>
</div>
<div class="form-group row">
<label for="custom_head" class="col-sm-4 col-form-label">{{ lang('theme') }}</label>
<label for="themes" class="col-sm-4 col-form-label">{{ lang('theme') }}</label>
<div class="col-sm-8">
<select class="form-control" id="themes" name="css">
<option id="theme-load" selected disabled hidden>{{ lang('click_to_load') }}</option>
@ -90,6 +90,13 @@
</div>
</div>
<div class="form-group row">
<label for="quota_enabled" class="col-sm-4 col-form-label">{{ lang('quota_enabled') }}</label>
<div class="col-sm-8">
<input type="checkbox" name="quota_enabled" data-toggle="toggle" {{ quota_enabled == 'on' ? 'checked' }}>
</div>
</div>
<div class="form-group row">
<label for="default_user_quota" class="col-sm-4 col-form-label">{{ lang('default_user_quota') }}</label>
<div class="col-sm-8">
@ -98,7 +105,7 @@
</div>
<div class="form-group row">
<label for="custom_head" class="col-sm-4 col-form-label">{{ lang('enforce_language') }}</label>
<label for="lang" class="col-sm-4 col-form-label">{{ lang('enforce_language') }}</label>
<div class="col-sm-8">
<select class="form-control" id="lang" name="lang">
<option value="auto">({{ lang('auto_set') }})</option>

View file

@ -7,7 +7,7 @@
<div class="container">
{% include 'comp/alert.twig' %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="col-md-10">
<div class="card shadow-sm">
<div class="card-header">{{ lang('user.create') }}</div>
<div class="card-body">
@ -31,25 +31,15 @@
</div>
</div>
<div class="form-group row">
<div class="col-sm-2"></div>
<label for="is_admin" class="col-sm-2 col-form-label">{{ lang('is_admin') }}</label>
<div class="col-sm-10">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_admin" name="is_admin">
<label class="form-check-label" for="is_admin">
{{ lang('is_admin') }}
</label>
</div>
<input type="checkbox" name="is_admin" data-toggle="toggle" data-off="{{ lang('no') }}" data-on="{{ lang('yes') }}">
</div>
</div>
<div class="form-group row">
<div class="col-sm-2"></div>
<label for="is_active" class="col-sm-2 col-form-label">{{ lang('is_active') }}</label>
<div class="col-sm-10">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_active" name="is_active" checked>
<label class="form-check-label" for="is_active">
{{ lang('is_active') }}
</label>
</div>
<input type="checkbox" name="is_active" data-toggle="toggle" data-off="{{ lang('no') }}" data-on="{{ lang('yes') }}" checked>
</div>
</div>
<div class="form-group row justify-content-md-end">

View file

@ -7,7 +7,7 @@
<div class="container">
{% include 'comp/alert.twig' %}
<div class="row justify-content-center">
<div class="col-md-8">
<div class="col-md-10">
<div class="card shadow-sm">
{% if not profile %}
<div class="card-header">{{ lang('user.edit') }}</div>
@ -67,25 +67,15 @@
</div>
{% if not profile %}
<div class="form-group row">
<div class="col-sm-2"></div>
<label for="is_admin" class="col-sm-2 col-form-label">{{ lang('is_admin') }}</label>
<div class="col-sm-10">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_admin" name="is_admin" {{ user.is_admin ? 'checked' }}>
<label class="form-check-label" for="is_admin">
{{ lang('is_admin') }}
</label>
</div>
<input type="checkbox" name="is_admin" data-toggle="toggle" data-off="{{ lang('no') }}" data-on="{{ lang('yes') }}" {{ user.is_admin ? 'checked' }}>
</div>
</div>
<div class="form-group row">
<div class="col-sm-2"></div>
<label for="is_active" class="col-sm-2 col-form-label">{{ lang('is_active') }}</label>
<div class="col-sm-10">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="is_active" name="is_active" {{ user.active ? 'checked' }}>
<label class="form-check-label" for="is_active">
{{ lang('is_active') }}
</label>
</div>
<input type="checkbox" name="is_active" data-toggle="toggle" data-off="{{ lang('no') }}" data-on="{{ lang('yes') }}" {{ user.active ? 'checked' }}>
</div>
</div>
{% endif %}