浏览代码

Working on password recovery

Sergio Brighenti 5 年之前
父节点
当前提交
9d3d85f739

+ 84 - 0
app/Controllers/Auth/PasswordRecoveryController.php

@@ -4,7 +4,91 @@
 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;
 
 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
+    {
+
+    }
 }

+ 5 - 0
app/routes.php

@@ -2,6 +2,7 @@
 
 // Auth routes
 use App\Controllers\AdminController;
+use App\Controllers\Auth\PasswordRecoveryController;
 use App\Controllers\Auth\RegisterController;
 use App\Controllers\ClientController;
 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->post('/register', [RegisterController::class, 'register'])->setName('register');
 $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->post('/login', [LoginController::class, 'login'])->setName('login');
 $app->map(['GET', 'POST'], '/logout', [LoginController::class, 'logout'])->setName('logout');

+ 9 - 1
resources/lang/en.lang.php

@@ -118,7 +118,15 @@ return [
     'password_recovery' => 'Recover password',
     'no_account' => 'Don\'t have an account?',
     'register' => 'Register',
+    'register_success' => 'The account has been created, a confirmation email has been sent.',
     '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"
+    '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',
 ];

+ 3 - 4
resources/templates/auth/login.twig

@@ -36,17 +36,16 @@
             <div class="row">
                 <div class="col-md-12">
                     <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>
-                    <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="form-check">
                             <input type="checkbox" name="remember" class="form-check-input float-left" id="remember">
                             <label class="form-check-label" for="remember">{{ lang('remember_me') }}</label>
                         </div>
-                        <a href="#" class="">{{ lang('password_recovery') }}</a>
+                        <a href="{{ route('recover') }}" class="">{{ lang('password_recovery') }}</a>
                     </div>
-
                 </div>
             </div>
             <div class="row mt-2">

+ 52 - 0
resources/templates/auth/recover_mail.twig

@@ -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 %}

+ 52 - 0
resources/templates/auth/recover_password.twig

@@ -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 %}

+ 4 - 4
resources/templates/auth/register.twig

@@ -1,6 +1,6 @@
 {% extends 'base.twig' %}
 
-{% block title %}{{ lang('login') }}{% endblock %}
+{% block title %}{{ lang('register') }}{% endblock %}
 
 {% block head %}
     <style>
@@ -36,11 +36,11 @@
             <div class="row">
                 <div class="col-md-12">
                     <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>
-                    <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>
-                    <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 class="row mt-2">

+ 3 - 3
src/css/app.css

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