Working on password recovery

This commit is contained in:
Sergio Brighenti 2020-02-27 18:14:08 +01:00
parent f30d2b4011
commit 9d3d85f739
8 changed files with 212 additions and 12 deletions

View file

@ -4,7 +4,91 @@
namespace App\Controllers\Auth; namespace App\Controllers\Auth;
use App\Controllers\Controller; use App\Controllers\Controller;
use App\Web\Mail;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
class PasswordRecoveryController extends Controller class PasswordRecoveryController extends Controller
{ {
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function recover(Request $request, Response $response): Response
{
return view()->render($response, 'auth/recover_mail.twig');
}
/**
* @param Request $request
* @param Response $response
* @return Response
* @throws \Exception
*/
public function recoverMail(Request $request, Response $response): Response
{
if ($this->session->get('logged', false)) {
return redirect($response, route('home'));
}
$user = $this->database->query('SELECT `id`, `username` FROM `users` WHERE `email` = ? LIMIT 1', param($request, 'email'))->fetch();
if (!isset($user->id)) {
$this->session->alert(lang('recover_email_sent'), 'success');
return redirect($response, route('recover'));
}
$resetToken = bin2hex(random_bytes(16));
$this->database->query('UPDATE `users` SET `reset_token`=? WHERE `id` = ?', [
$resetToken,
$user->id,
]);
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.recover_password', [$this->config['app_name']]))
->message(lang('mail.recover_text', [
$user->username,
route('recover.password', ['resetToken' => $resetToken]),
]))
->send();
$this->session->alert(lang('recover_email_sent'), 'success');
return redirect($response, route('recover'));
}
/**
* @param Request $request
* @param Response $response
* @param string $resetToken
* @return Response
* @throws \Twig\Error\LoaderError
* @throws \Twig\Error\RuntimeError
* @throws \Twig\Error\SyntaxError
*/
public function recoverPasswordForm(Request $request, Response $response, string $resetToken): Response
{
return view()->render($response, 'auth/recover_password.twig', [
'reset_token' => $resetToken
]);
}
/**
* @param Request $request
* @param Response $response
* @param string $resetToken
* @return Response
*/
public function recoverPassword(Request $request, Response $response, string $resetToken): Response
{
}
} }

View file

@ -2,6 +2,7 @@
// Auth routes // Auth routes
use App\Controllers\AdminController; use App\Controllers\AdminController;
use App\Controllers\Auth\PasswordRecoveryController;
use App\Controllers\Auth\RegisterController; use App\Controllers\Auth\RegisterController;
use App\Controllers\ClientController; use App\Controllers\ClientController;
use App\Controllers\DashboardController; use App\Controllers\DashboardController;
@ -66,6 +67,10 @@ $app->get('/', [DashboardController::class, 'redirects'])->setName('root');
$app->get('/register', [RegisterController::class, 'registerForm'])->setName('register.show'); $app->get('/register', [RegisterController::class, 'registerForm'])->setName('register.show');
$app->post('/register', [RegisterController::class, 'register'])->setName('register'); $app->post('/register', [RegisterController::class, 'register'])->setName('register');
$app->get('/activate/{activateToken}', [RegisterController::class, 'activateUser'])->setName('activate'); $app->get('/activate/{activateToken}', [RegisterController::class, 'activateUser'])->setName('activate');
$app->get('/recover', [PasswordRecoveryController::class, 'recover'])->setName('recover');
$app->post('/recover/mail', [PasswordRecoveryController::class, 'recoverMail'])->setName('recover.mail');
$app->get('/recover/password/{resetToken}', [PasswordRecoveryController::class, 'recoverPasswordForm'])->setName('recover.password.view');
$app->post('/recover/password/{resetToken}', [PasswordRecoveryController::class, 'recoverPassword'])->setName('recover.password');
$app->get('/login', [LoginController::class, 'show'])->setName('login.show'); $app->get('/login', [LoginController::class, 'show'])->setName('login.show');
$app->post('/login', [LoginController::class, 'login'])->setName('login'); $app->post('/login', [LoginController::class, 'login'])->setName('login');
$app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout'); $app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout');

View file

@ -118,7 +118,15 @@ return [
'password_recovery' => 'Recover password', 'password_recovery' => 'Recover password',
'no_account' => 'Don\'t have an account?', 'no_account' => 'Don\'t have an account?',
'register' => 'Register', 'register' => 'Register',
'register_success' => 'The account has been created, a confirmation email has been sent.',
'default_user_quota' => 'Default User Quota', 'default_user_quota' => 'Default User Quota',
'invalid_quota' => 'Invalid values as 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" '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",
'mail.activate_account' => '%s - Account Activation',
'mail.recover_text' => "Hi %s,\na password reset has been requested for your account. To complete the procedure click on the following link:\n\n%s\n\nIf it wasn't you who requested the password reset, simply ignore this email.",
'mail.recover_password' => '%s - Password Recovery',
'recover_email_sent' => 'If present, a recovery email was sent to the specified account.',
'account_activated' => 'Account activated, now you can login!',
'quota_enabled' => 'Enable user quota',
'password_repeat' => 'Repeat Password',
]; ];

View file

@ -36,17 +36,16 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<label for="username" class="sr-only">{{ lang('login.username') }}</label> <label for="username" class="sr-only">{{ lang('login.username') }}</label>
<input type="text" id="username" class="form-control" placeholder="{{ lang('login.username') }}" name="username" required autofocus> <input type="text" id="username" class="form-control first" placeholder="{{ lang('login.username') }}" name="username" required autofocus>
<label for="password" class="sr-only">{{ lang('password') }}</label> <label for="password" class="sr-only">{{ lang('password') }}</label>
<input type="password" id="password" class="form-control" placeholder="{{ lang('password') }}" name="password" required> <input type="password" id="password" class="form-control last" placeholder="{{ lang('password') }}" name="password" required>
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div class="form-check"> <div class="form-check">
<input type="checkbox" name="remember" class="form-check-input float-left" id="remember"> <input type="checkbox" name="remember" class="form-check-input float-left" id="remember">
<label class="form-check-label" for="remember">{{ lang('remember_me') }}</label> <label class="form-check-label" for="remember">{{ lang('remember_me') }}</label>
</div> </div>
<a href="#" class="">{{ lang('password_recovery') }}</a> <a href="{{ route('recover') }}" class="">{{ lang('password_recovery') }}</a>
</div> </div>
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">

View file

@ -0,0 +1,52 @@
{% extends 'base.twig' %}
{% block title %}{{ lang('password_recovery') }}{% endblock %}
{% block head %}
<style>
html {
height: 100%;
}
body {
height: 100%;
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-bottom: 40px;
margin-bottom: 0;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<form class="form-signin" method="post" action="{{ route('recover.mail') }}">
<div class="row text-center">
<div class="col-md-12">
<h1 class="h3 mb-3 font-weight-normal">{{ config.app_name }}</h1>
{% include 'comp/alert.twig' %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<label for="email" class="sr-only">{{ lang('email') }}</label>
<input type="email" id="email" class="form-control" placeholder="mail@example.com" name="email" required autofocus>
</div>
</div>
<div class="row mt-2">
<div class="col-md-12">
<button class="btn btn-lg btn-primary btn-block" type="submit">{{ lang('password_recovery') }}</button>
<div class="text-center mt-2">
<a href="{{ route('login.show') }}">{{ lang('cancel') }}</a>
</div>
</div>
</div>
</form>
</div>
{% endblock %}

View file

@ -0,0 +1,52 @@
{% extends 'base.twig' %}
{% block title %}{{ lang('password_recovery') }}{% endblock %}
{% block head %}
<style>
html {
height: 100%;
}
body {
height: 100%;
display: -ms-flexbox;
display: -webkit-box;
display: flex;
-ms-flex-align: center;
-ms-flex-pack: center;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
padding-bottom: 40px;
margin-bottom: 0;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<form class="form-signin" method="post" action="{{ route('recover.mail') }}">
<div class="row text-center">
<div class="col-md-12">
<h1 class="h3 mb-3 font-weight-normal">{{ config.app_name }}</h1>
{% include 'comp/alert.twig' %}
</div>
</div>
<div class="row">
<div class="col-md-12">
<input type="hidden" name="reset_token" value="{{ reset_token }}">
<label for="password" class="sr-only">{{ lang('password') }}</label>
<input type="password" id="password" class="form-control first" placeholder="{{ lang('password') }}" name="password" required>
<label for="password_repeat" class="sr-only">{{ lang('password_repeat') }}</label>
<input type="password" id="password_repeat" class="form-control last" placeholder="{{ lang('password_repeat') }}" name="password_repeat" required>
</div>
</div>
<div class="row mt-2">
<div class="col-md-12">
<button class="btn btn-lg btn-primary btn-block" type="submit">{{ lang('password_recovery') }}</button>
</div>
</div>
</form>
</div>
{% endblock %}

View file

@ -1,6 +1,6 @@
{% extends 'base.twig' %} {% extends 'base.twig' %}
{% block title %}{{ lang('login') }}{% endblock %} {% block title %}{{ lang('register') }}{% endblock %}
{% block head %} {% block head %}
<style> <style>
@ -36,11 +36,11 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<label for="username" class="sr-only">{{ lang('username') }}</label> <label for="username" class="sr-only">{{ lang('username') }}</label>
<input type="text" id="username" class="form-control" placeholder="{{ lang('username') }}" name="username" required autofocus> <input type="text" id="username" class="form-control first" placeholder="{{ lang('username') }}" name="username" required autofocus>
<label for="email" class="sr-only">E-Mail</label> <label for="email" class="sr-only">E-Mail</label>
<input type="email" id="email" class="form-control" placeholder="mail@example.com" name="email" required> <input type="email" id="email" class="form-control middle" placeholder="mail@example.com" name="email" required>
<label for="password" class="sr-only">{{ lang('password') }}</label> <label for="password" class="sr-only">{{ lang('password') }}</label>
<input type="password" id="password" class="form-control" placeholder="{{ lang('password') }}" name="password" required> <input type="password" id="password" class="form-control last" placeholder="{{ lang('password') }}" name="password" required>
</div> </div>
</div> </div>
<div class="row mt-2"> <div class="row mt-2">

View file

@ -28,18 +28,18 @@ body {
z-index: 2; z-index: 2;
} }
.form-signin input[type="text"] { .form-signin input.first {
margin-bottom: -1px; margin-bottom: -1px;
border-bottom-right-radius: 0; border-bottom-right-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
.form-signin input[type="email"] { .form-signin input.middle {
margin-bottom: -1px; margin-bottom: -1px;
border-radius: 0; border-radius: 0;
} }
.form-signin input[type="password"] { .form-signin input.last {
margin-bottom: .50rem; margin-bottom: .50rem;
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;