Přihlašování do aplikace

Rudolf Svátek 2017-08-21 13:26

Mít aplikaci na správu hesel a dovolit komukoli s ní pracovat? To není dobrý nápad :-). Musíme si aplikaci ošetřit tak, aby se do ní musel uživatel přihlásit a teprve potom mu zobrazíme výpis dat. Čili v první řadě musí mít možnost přihlášení se.

K tomu už má Nette Sandbox přichystány presentery i šablony. Ale my si je trochu upravíme. Jsou to všechno formuláře, takže bychom je měli přesunout do \app\forms. Definici formuláře v SignInFormFactory lehce upravíme takto:

<?php

namespace App\Forms;

use Nette;
use Nette\Application\UI\Form;
use Nette\Security\User;

/**
 * Class SignInFormFactory
 *
 * @package App\Forms
 */
class SignInFormFactory
{
    use Nette\SmartObject;

    /** @var FormFactory */
    private $factory;

    /** @var User */
    private $user;

    /**
     * SignInFormFactory constructor.
     *
     * @param \App\Forms\FormFactory $factory
     * @param \Nette\Security\User   $user
     */
    public function __construct(FormFactory $factory, User $user)
    {
        $this->factory = $factory;
        $this->user = $user;
    }

    /**
     * Method create
     *
     * @param callable $onSuccess
     *
     * @return \Nette\Application\UI\Form
     */
    public function create(callable $onSuccess)
    {
        $form = $this->factory->create();
        $form->addText('username', 'Uživatelské jméno:')
            ->setRequired('Zadejte prosím svůj username');
        $form->addPassword('password', 'Password:')
            ->setRequired('Zadejte prosím své heslo');
        $form->addCheckbox('remember', 'Neodhlašovat');
        $form->addSubmit('send', 'Přihlásit');

        $form->onSuccess[] = function (Form $form, $values) use ($onSuccess) {
            try {
                $this->user->setExpiration($values->remember ? '14 days' : '20 minutes');
                $this->user->login($values->username, $values->password);
            } catch (Nette\Security\AuthenticationException $e) {
                $form->addError('Nesprávné přihlašovací údaje.');
                return;
            }
            $onSuccess();
        };
        return $form;
    }
}

Tam těch změn tolik není. Jde vlastně jen o češtinu a komentáře. Ale na presenteru a šablonách se vyřádíme. Jak jsem už říkal několikrát - ideální je, když presenter dělá jen jednu věc. Měl by tedy mít jen jednu akci actionDefault a tam řeší co potřebuje. SignPresenter ze Sandboxu ale dělá věci 2 - přihlašování i odhlašování. V dalším díle navíc uvidíme, že potřebujeme ještě řešit zapomenuté heslo a to jsou 2 další akce. Takže presenter rozdělíme. Presenter pro přihlášení by měl vypadat takto:

<?php

namespace App\Presenters;

use App\Forms;
use Dibi\Connection;
use Nette\Application\UI\Presenter;

/**
 * Class SignInPresenter
 *
 * @package App\Presenters
 */
class SignInPresenter extends Presenter
{
    /** @var Forms\SignInFormFactory @inject */
    public $signInFactory;

    /** @var Connection */
    private $db;

    /**
     * SignInPresenter constructor.
     *
     * @param Connection              $db
     */
    public function __construct(Connection $db)
    {
        parent::__construct();
        $this->db = $db;
    }

    /**
     * Method actionIn
     */
    public function actionDefault()
    {
        $this->getTemplate()->setFile(__DIR__ . "/templates/in.latte");
    }

    /**
     * Sign-in form factory.
     *
     * @return \Nette\Application\UI\Form
     */
    protected function createComponentSignInForm()
    {
        return $this->signInFactory->create(
            function () {
                $this->redirect('Passwords:');
            }
        );
    }
}

Je tam jen konstruktor, kde si předávám továrničku přihlašovacího formuláře, vytvoření komponenty přihlašovacího formuláře a ošetření akcí pro přihlášení. Odhlášení pak řeším v jiném presenteru:

<?php

namespace App\Presenters;

use Nette\Application\UI\Presenter;

/**
 * Class SignOutPresenter
 *
 * @package App\Presenters
 */
class SignOutPresenter extends Presenter
{
    /**
     * Method actionOut
     */
    public function actionDefault()
    {
        $this->getUser()->logout();
        $this->redirect('SignIn:');
    }
}

V presenteru SignIn (a později i dalších, které budou řešit problematiku zapomenutého hesla) budu používat jiný layout. Proto vytvořím soubor \app\presenters\Sign\templates\@layout.latte:

<!DOCTYPE html>
<html lang="cs">
  <head>
    <meta charset="UTF-8"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
    <link rel="stylesheet" href="//cdn.rawgit.com/twbs/bootstrap/v3.3.6/dist/css/bootstrap.min.css">
    <link rel="stylesheet" type="text/css" href="{$basePath}/css/login.css"/>
  </head>
  <body>
    <div class="container">
      {include content}
    </div>
    <script src="//cdn.rawgit.com/nette/forms/v2.2.4/src/assets/netteForms.js"></script>
  </body>
</html>

Šablonu pro vykreslení přihlašovacího formuláře si klidně nech výchozí, ale já si vymakal a oblíbil tuto:

{block content}
    <div class="form-2" n:if="$flashes">
        {snippet flash}
            <div n:foreach="$flashes as $flash" class="alert alert-{$flash->type} fade in">
                {$flash->message}
            </div>
        {/snippet}
    </div>

    <section class="main">
        <form n:name=signInForm class="form-2">
            <h1><span class="log-in"> Přihlásit</span></h1>
            <ul class="errors" n:if="$form->hasErrors()">
                <li n:foreach="$form->errors as $error">{$error}</li>
            </ul>
            <p class="float">
                <label n:name=username><i class="fa fa-user"></i>Uživatelské jméno</label>
                <input type="text" tabindex="1" n:name=username placeholder="Uživatelské jméno">
                <label n:name=remember><input type="checkbox" tabindex="3" n:name=remember>neodhlašovat</label>
            </p>
            <p class="float">
                <label n:name=password><i class="fa fa-lock"></i>heslo</label>
                <input type="password" tabindex="2" n:name=password placeholder="Heslo" class="showpassword">
                <label for="showPassword"><input id="showPassword" tabindex="4" class="showpasswordcheckbox"
                                                 type="checkbox">ukázat</label>
            </p>
            <p class="clearfix">
                <input type="submit" n:name=send>
            </p>
            <p>
                <a n:href="SignRemember:" class="btn btn-link btn-sm" role="button"><i class="fa fa-mail-reply-all"></i>
                    Zapomenuté heslo</a>
            </p>
        </form>
        ​​
    </section>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    <script type="text/javascript">
        $(function () {
            $(".showpassword").each(function (index, input) {
                var $input = $(input);
                $("#showPassword").click(function () {
                    var change = $(this).is(":checked") ? "text" : "password";
                    var rep = $("<input placeholder='Heslo' type='" + change + "' />")
                        .attr("id", $input.attr("id"))
                        .attr("name", $input.attr("name"))
                        .attr('class', $input.attr('class'))
                        .val($input.val())
                        .insertBefore($input);
                    $input.remove();
                    $input = rep;
                })
            });
            $('#showPassword').click(function () {
                if ($("#showPassword").is(":checked")) {
                    $('.icon-lock').addClass('icon-unlock');
                    $('.icon-unlock').removeClass('icon-lock');
                } else {
                    $('.icon-unlock').addClass('icon-lock');
                    $('.icon-lock').removeClass('icon-unlock');
                }
            });
        });
    </script>
{/block}

{block title}
    Přihlášení
{/block}

!!!POZOR!!!

V šabloně je odkaz na presenter SignRemember, ale ten ještě neexistuje. Budeme ho vyrábět až v další kapitole. Zatím ho tedy můžeš zakomentovat, nebo smazat. Mně celkem vyhovuje takové odkazy nechat, ale místo n:href="" to změnit jen na href="".

Aby to trochu vypadalo, můžeš si stáhnout styly.

Šablona umí pár hezkých věcí, jako pamatovat si přihlášení, nebo ukázat zadávané heslo. No a to je vlastně v tuhle chvíli všechno potřebné k tomu, aby se uživatel mohl přihlásit. Zadej http://localhost/passwords/www/sign-in a měl bys vidět přihlašovací obrazovku. Když pak zadáš správné přihlašovací údaje, uvidíš v Tracy, že jsi přihlášen.

Odhlášení pak probíhá na adrese http://localhost/passwords/www/sign-out. Aby to bylo pro přihlášeného uživatele pohodlnější, můžeme přidat do šablony @layout.latte odkaz pro odhlášení:

		<nav class="navbar navbar-static-top">
			<a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
				<span class="sr-only">Toggle navigation</span>
			</a>
			<div class="navbar-custom-menu">
				<ul class="nav navbar-nav">
					<li><a n:href="SignOut"><span class="glyphicon glyphicon-log-out"></span>
							Odhlásit</a></li>
				</ul>
			</div>
		</nav>

Registraci uživatelů bych u této aplikace asi nedovoloval. Smazal jsem proto v app\forms soubor SignUpFormFactory.php a také odstranil odpovídající řádek v konfiguračním souboru config.form.neon.

Pokud chceme zajistit, aby do aplikace neměl nepřihlášený uživatel přístup, doplníme BasePresenter (ze kterého dědí všechny ostatní presentery kromě SignPresenter.php) následující metodu:

    /**
     * Method startup
     */
    protected function startup()
    {
        parent::startup();

        if (!$this->user->isLoggedIn()) {
            if ($this->user->logoutReason === Nette\Security\IUserStorage::INACTIVITY) {
                $this->flashMessage('Byli jste odhlášeni z důvodu nečinnosti. Prosím, přihlaste se znovu.', 'alert-info');
            } else {
                $this->flashMessage('Prosím, přihlaste se.', 'alert-info');
            }
            $this->redirect('SignIn:');
        }
    }

Prostě se zjistí, jestli je uživatel přihlášen, a když ne, donutí ho to přihlásit se. Je samozřejmě možné jít dál a vyžadovat nějaký určitý typ oprávnění, ale naše aplikace je celkem malá a tak prostě každý přihlášený uživatel může pracovat.

No a to je dnes asi opravdu všechno. Příště ještě vyřešíme c dělat, když uživatel zapomene heslo.

Redakční systém RS::RS Předchozí kapitola

Doplnění dalších komponent

Redakční systém RS::RS Celý seriál

Aplikace na správu hesel

Redakční systém RS::RS Následující kapitola

Zapomenuté heslo pro přihlášení

Komentáře (0)

Přidej svůj komentář

O mně

Jmenuji se Rudolf Svátek. Jsem lektor výpočetní techniky a PHP programátor. Stavím firemní stránky a eshopy. Aby se mi to dělalo pohodlně, vytvořil jsem redakční systém RS::RS, který ti tu nabízím k použití.

Rychlý kontakt na mně

  • Rudolf Svátek
  • Telefon:
    +420 777 828 353
  • Email:
  • Adresa:
    Josefa Hory 1097/5
    736 01 Havířov
    ČR



Tyto stránky používají Cookies. Používáním stránek s tím souhlasíte Další informace