Príklad modulu Users
Postavte živý modul Users pre DotApp 2.0: samostatné prihlásenie, registráciu,
dvojfaktorové potvrdenie, chránenú stránku aplikácie a AJAX formuláre poháňané
dotapp.js.
Otvoriť živú ukážku.
Prehľad
Živý modul sa nachádza v /app/modules/Users. DotApp smeruje požiadavky
priamo na verejné statické metódy kontroléra, napríklad
Users:Login@page!, Users:Login@save! a
Users:Register@save!.
Predvolený prefix URL sa konfiguruje pomocou
Config::module('Users', 'prefix') a predvolene je
/documentation/examples/run/users. Návod nižšie používa tieto trasy /users.
Predpoklady
Tento príklad predpokladá, že poznáte tok zabezpečených formulárov: DotApp podpisuje formuláre, overuje
odoslania pomocou $request->crcCheck() a vracia JSON cez
DotApp::DotApp()->ajaxReply($body, $code).
- Zabezpečené formuláre s dotapp.js
- Databáza MySQL nakonfigurovaná v
/app/config.php. - Autentifikačné tabuľky frameworku pripravené príkazom
php dotapper.php --prepare-database.
Vytvorenie modulu
Modul, dva kontroléry a middleware brány trás vytvorte pomocou DotApper:
php dotapper.php --create-module=Users
php dotapper.php --module=Users --create-controller=Login
php dotapper.php --module=Users --create-controller=Register
php dotapper.php --module=Users --create-middleware=AuthGate
Živý modul obsahuje Controllers/Login.php,
Controllers/Register.php, Middleware/AuthGate.php,
samostatné view v views/ a frontend skript v
assets/js/users.js.
Nastavenie databázy
Tabuľky používateľov a autentifikácie sú tabuľky frameworku generované príkazom na prípravu databázy. Používajú váš nakonfigurovaný prefix databázy.
php dotapper.php --prepare-database
Demo potrebuje nakonfigurované MySQL, inak registrácia ani prihlásenie nebudú fungovať. Ak je
zoznam nakonfigurovaných databáz prázdny, volania ako Auth::createUser() a
Auth::login() zlyhajú a kontroléry vrátia priateľskú správu o nedostupnosti.
Konfigurácia
Databázu nakonfigurujte v /app/config.php. Presné prihlasovacie údaje závisia
od vášho lokálneho prostredia:
Config::db('driver', 'pdo');
Config::addDatabase('main', '127.0.0.1', 'Username', 'Password', 'DBNAME', 'UTF8', 'MYSQL', 'pdo');
Modul pri štarte inicializuje svoj prefix:
Config::module('Users', 'prefix') ?? Config::module('Users', 'prefix', '/documentation/examples/run/users');
$p = rtrim((string) Config::module('Users', 'prefix'), '/');
Users.prefix prepíšte v konfigurácii projektu, keď chcete ten istý
modul namontovať inde.
Kontroléry
DotApp volá verejné statické metódy kontroléra. Koncové ! v cieľoch trás
označuje metódy ako page(), save(),
twoFactorPage(), twoFactorSave(), app() a
logout(). Tieto metódy nepoužívajú dependency injection.
View sa vykresľujú pomocou Renderer::new()->module('Users')->setView($name),
po ktorom nasledujú volania setViewVar(). Ak renderer vráti prázdny
reťazec, živé kontroléry vrátia new Response(500, 'Template error').
Stránka prihlásenia a tok uloženia
<?php
class Login extends \Dotsystems\App\Parts\Controller
{
public static function page($request)
{
if (Auth::isLogged()) {
return Response::redirect(self::prefix() . '/app', 302);
}
return self::view('login', 'Sign in', self::prefix() . '/login');
}
public static function save($request)
{
if (!$request->crcCheck()) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Bad request'], 400);
}
$answer = $request->form(['POST'], 'loginForm', function ($request) {
$payload = $request->data(true)['data'] ?? [];
$email = trim((string) ($payload['email'] ?? ''));
$password = (string) ($payload['password'] ?? '');
$remember = (($payload['remember'] ?? '') === 'on');
$login = Auth::login(['email' => $email, 'password' => $password, 'stage' => 0], $remember);
if (Auth::loggedStage() === 2) {
return ['code' => 200, 'body' => [
'status' => 1,
'twofactor' => 1,
'redirectTo' => self::prefix() . '/2fa',
]];
}
return ['code' => 200, 'body' => ['status' => 1, 'redirectTo' => self::prefix() . '/app']];
}, function () {
return ['code' => 403, 'body' => ['status' => 0, 'message' => 'Invalid signature']];
}, $request->getPath());
return DotApp::DotApp()->ajaxReply($answer['body'], $answer['code']);
}
}
Tok uloženia registrácie
<?php
class Register extends \Dotsystems\App\Parts\Controller
{
public static function save($request)
{
if (!$request->crcCheck()) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Bad request'], 400);
}
$answer = $request->form(['POST'], 'registerForm', function ($request) {
$data = $request->data(true)['data'] ?? [];
$email = trim((string) ($data['email'] ?? ''));
$username = trim((string) ($data['username'] ?? ''));
$password = (string) ($data['password'] ?? '');
$r = Auth::createUser($username, $password, $email);
if (($r['error'] ?? 99) === 1) {
return ['code' => 200, 'body' => ['status' => 0, 'message' => 'That account already exists.']];
}
$p = rtrim((string) Config::module('Users', 'prefix'), '/');
return ['code' => 200, 'body' => [
'status' => 1,
'message' => 'Account created.',
'redirectTo' => $p . '/login',
]];
}, function () {
return ['code' => 403, 'body' => ['status' => 0, 'message' => 'Invalid signature']];
}, $request->getPath());
return DotApp::DotApp()->ajaxReply($answer['body'], $answer['code']);
}
}
Metódy dvojfaktorového overenia a aplikácie
<?php
public static function twoFactorPage($request)
{
if (Auth::isLogged()) {
return Response::redirect(self::prefix() . '/app', 302);
}
if (Auth::loggedStage() !== 2) {
return Response::redirect(self::prefix() . '/login', 302);
}
return self::view('twofactor', 'Authenticator code', self::prefix() . '/2fa');
}
public static function twoFactorSave($request)
{
if (!$request->crcCheck()) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Bad request'], 400);
}
$data = $request->data(true)['data'] ?? [];
$code = preg_replace('/\D+/', '', (string) ($data['tfa'] ?? ''));
$r = Auth::confirmTwoFactor(['tfa' => $code]);
if (!is_array($r) || ($r['confirmed'] ?? false) !== true) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'message' => 'Verification failed'], 200);
}
return DotApp::DotApp()->ajaxReply(['status' => 1, 'redirectTo' => self::prefix() . '/app'], 200);
}
public static function app($request)
{
$attrs = Auth::attributes();
$email = htmlspecialchars((string) ($attrs['email'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$html = Renderer::new()->module('Users')
->setView('app')
->setViewVar('title', 'Signed in')
->setViewVar('email', $email)
->setViewVar('logoutAction', self::prefix() . '/logout')
->setViewVar('docsUrl', '/documentation/examples/users-module')
->renderView();
return $html === '' ? new Response(500, 'Template error') : $html;
}
Relevantné volania autentifikácie sú Auth::login(), Auth::createUser(),
Auth::confirmTwoFactor(), Auth::isLogged(),
Auth::loggedStage() === 2, Auth::logout() a
Auth::can($rights).
Middleware
Živé middleware je AuthGate. Na chránené trasy sa pripája pomocou
->before('#Users:AuthGate@check!'); samo nie je handlerom trasy
a nepotrebuje samostatnú registráciu aliasu.
<?php
namespace Dotsystems\App\Modules\Users\Middleware;
use Dotsystems\App\Parts\Auth;
use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\Response;
class AuthGate extends \Dotsystems\App\Parts\ModuleMiddleware
{
public static function check($request, array $rights = [])
{
if (!Auth::isLogged()) {
$p = rtrim((string) Config::module('Users', 'prefix'), '/');
return Response::redirect($p . '/login', 302);
}
if (!empty($rights) && !Auth::can($rights)) {
return new Response(403, 'Forbidden');
}
}
}
Trasy
Trasy sa konfigurujú v /app/modules/Users/module.init.php. Každá trasa sa
registruje ako pár, takže funguje cesta aj verzia s koncovou lomkou.
<?php
Config::module('Users', 'prefix') ?? Config::module('Users', 'prefix', '/documentation/examples/run/users');
$p = rtrim((string) Config::module('Users', 'prefix'), '/');
$pair = function (string $path): array {
$path = rtrim($path, '/');
return [$path, $path . '/'];
};
Router::get($pair($p), 'Users:Login@page!', Router::STATIC_ROUTE);
Router::get($pair($p . '/login'), 'Users:Login@page!', Router::STATIC_ROUTE);
Router::post($pair($p . '/login'), 'Users:Login@save!', Router::STATIC_ROUTE);
Router::get($pair($p . '/2fa'), 'Users:Login@twoFactorPage!', Router::STATIC_ROUTE);
Router::post($pair($p . '/2fa'), 'Users:Login@twoFactorSave!', Router::STATIC_ROUTE);
Router::post($pair($p . '/logout'), 'Users:Login@logout!', Router::STATIC_ROUTE);
Router::get($pair($p . '/register'), 'Users:Register@page!', Router::STATIC_ROUTE);
Router::post($pair($p . '/register'), 'Users:Register@save!', Router::STATIC_ROUTE);
Router::get($pair($p . '/app'), 'Users:Login@app!', Router::STATIC_ROUTE)
->before('#Users:AuthGate@check!');
View
Živé view sú kompletné HTML dokumenty. Zahŕňajú CSS modulu, skript frameworku
/assets/dotapp/dotapp.js a skript modulu
/assets/modules/Users/js/users.js.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ var: $title }} — DotApp</title>
<link rel="stylesheet" href="/assets/modules/Users/css/users.css" />
</head>
<body class="u-body">
<header class="u-top"><a href="{{ var: $docsUrl }}">Documentation</a><span>Users demo</span></header>
<main class="u-main">
<h1>Sign in</h1>
<div id="error-message" class="u-error" hide="hide"></div>
<div id="loginWrap">
<fo-rm method="POST" id="loginForm">
<label for="email">Email</label>
<input type="text" id="email" name="email" autocomplete="username" required />
<label for="password">Password</label>
<input type="password" id="password" name="password" autocomplete="current-password" required />
{{ formName(loginForm) }}
<button type="submit" id="loginBtn" class="u-btn">Sign in</button>
</fo-rm>
</div>
<p><a href="{{ var: $registerUrl }}">Create an account</a></p>
</main>
<script src="/assets/dotapp/dotapp.js"></script>
<script src="/assets/modules/Users/js/users.js"></script>
</body>
</html>
Dôležité sú vlastný element <fo-rm>, značka šablóny
{{ formName(loginForm) }} medzi otváracou a zatváracou
značkou a aktuálne cesty k assetom.
Layouty
Živá ukážka zámerne nepoužíva vnorené layouty. Každý súbor v
/app/modules/Users/views/ je samostatná HTML stránka vykreslená priamo pomocou
setView('login'), setView('register'),
setView('twofactor') alebo setView('app').
<fo-rm method="POST" id="registerForm">
<label for="username">Username</label>
<input type="text" id="username" name="username" required />
<label for="email">Email</label>
<input type="text" id="email" name="email" autocomplete="username" required />
<label for="password">Password</label>
<input type="password" id="password" name="password" autocomplete="new-password" required />
{{ formName(registerForm) }}
<button type="submit" id="registerBtn" class="u-btn">Create account</button>
</fo-rm>
Moduly môžu view obaliť značkou {{ layout:name }} a volaniami renderera ako
setView('x', 'layout'). Táto ukážka ponecháva stránky samostatné, aby sa
príklad ľahko kopíroval.
Assety
Živé view načítavajú tieto assety:
/assets/modules/Users/css/users.cssna štýlovanie modulu./assets/dotapp/dotapp.jsna klientské pomocné funkcie DotApp a spracovanie podpísaných formulárov./assets/modules/Users/js/users.jsna správanie prihlásenia, registrácie, 2FA a odhlásenia.
<link rel="stylesheet" href="/assets/modules/Users/css/users.css" />
<script src="/assets/dotapp/dotapp.js"></script>
<script src="/assets/modules/Users/js/users.js"></script>
JavaScript
Frontend skript je čistý JavaScript používajúci $dotapp. Čaká na udalosť
dotapp, keď pomocná knižnica ešte nie je dostupná; jQuery nepoužíva.
(function () {
var runMe = function ($dotapp) {
if (document.getElementById("loginForm")) {
$dotapp()
.form("#loginForm")
.before(function (data, form) {
if ($dotapp(form).attr("blocked") == 1) return $dotapp().halt();
$dotapp(form).attr("blocked", "1");
$dotapp("#loginBtn").attr("loading", "true").attr("loader", "dots");
$dotapp("#loginWrap").addClass("u_busy");
$dotapp("#error-message").attr("hide", "hide");
})
.after(function (data, response, form) {
var reply = $dotapp().parseReply(response);
if (reply && reply.status == 1 && reply.redirectTo) {
window.location = reply.redirectTo;
return;
}
if (reply && reply.message) $dotapp("#error-message").attr("hide", "false").html(reply.message);
$dotapp(form).attr("blocked", "0");
$dotapp("#loginBtn").removeAttr("loading").removeAttr("loader");
$dotapp("#loginWrap").removeClass("u_busy");
});
}
if (document.querySelector(".two-fa-inputs input")) {
var box = document.querySelector(".two-fa-inputs");
var twoFaUrl = (box && box.getAttribute("data-2fa")) || "/documentation/examples/run/users/2fa";
$dotapp(".two-fa-inputs input").twoFactor(function (code) {
$dotapp().load(twoFaUrl, "POST", { tfa: code }, function (raw) {
var reply = $dotapp().parseReply(raw);
if (reply && reply.status == 1 && reply.redirectTo) window.location = reply.redirectTo;
else if (reply && reply.message) $dotapp("#error-message").attr("hide", "false").html(reply.message);
});
}, { length: 6, allowLetters: false, autoSubmit: true });
}
};
if (window.$dotapp) runMe(window.$dotapp);
else window.addEventListener("dotapp", function () { runMe(window.$dotapp); }, { once: true });
})();
Registrácia používa rovnaký vzor $dotapp().form("#registerForm").
Odhlásenie odosiela dáta pomocou $dotapp().load(url, "POST", {}, callback) z tlačidla
na stránke po prihlásení.
Živá ukážka
Vyskúšajte živú ukážku na adrese /documentation/examples/run/users/login alebo si vytvorte účet na /documentation/examples/run/users/register.
STIAHNUŤ na https://github.com/dotsystems-sk/moduleUsers