Zabezpečené formuláre s dotapp.js
Otvoriť živú ukážku na adrese /documentation/examples/run/forms2.
Tento príklad je živá stránka Examples:Forms@index2! / @submit2!. Hodnoty v selectoch sú v šablóne šifrované. Prehliadač odosiela dáta pomocou fo-rm, formName, CRC a dotapp.js. Stránka sa nenačítava znova.
Predpoklady
Začnite od príkladu pomenovaných formulárov, aby modul Examples a kontrolér Forms už existovali.
Vytvorenie view
Živá ukážka používa samostatný view v /app/modules/Examples/views/forms2.view.php. Súbor je kompletný HTML dokument.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ var: $title }} — DotApp PHP Framework 2.0</title>
<link rel="stylesheet" href="/assets/modules/Examples/css/examples.css" />
</head>
<body class="ex-body">
<header class="ex-top">
<a href="{{ var: $docsUrl }}">Documentation</a>
<span>Secure form</span>
</header>
<main class="ex-main">
<h1>Secure form</h1>
<p class="ex-lead">Select values are encrypted in the template. The post uses <code>fo-rm</code>, CRC, and <code>dotapp.js</code>.</p>
<div id="error-message" class="ex-error" hide="hide"></div>
<div id="status" class="ex-status" hide="hide"></div>
<div id="noteWrap" class="ex-card">
<fo-rm method="POST" id="example2" action="{{ var: $postAction }}">
<label for="name">Name</label>
<input type="text" id="name" name="name" required />
<label for="category">Category</label>
<select id="category" name="category" required>
<option value="" disabled selected>Select a category</option>
<option value="{{ enc(additionalKey): "SupportVal" }}">Support</option>
<option value="{{ enc(additionalKey): "FeedbackVal" }}">Feedback</option>
<option value="{{ enc(additionalKey): "OtherVal" }}">Other</option>
</select>
<label for="foreach">Select created by foreach</label>
<select id="foreach" name="foreach" required>
<option value="" disabled selected>Select a category again</option>
{{ foreach $items as $item }}
<option value="{{ enc(additionalKey2): $item['value'] }}">{{ var: $item['text'] }}</option>
{{ /foreach }}
</select>
{{ formName(CSRF) }}
<button type="submit" id="noteBtn" class="ex-btn">Submit</button>
</fo-rm>
<div class="output ex-result">Submit the form to see the result</div>
</div>
</main>
<script src="{{ var: $dotappJs }}"></script>
<script src="/assets/modules/Examples/js/forms2.js"></script>
</body>
</html>
Značku {{ formName(CSRF) }} umiestnite medzi značky <fo-rm>. Atribút action nastavte na aktuálnu cestu (živá ukážka používa $request->getPath()). Tú istú cestu odovzdajte ako posledný argument form(), aby sa handler zhodoval. Načítajte /assets/dotapp/dotapp.js s jedinečným query parametrom pri každom vykreslení stránky, aby sa session CSRF token nepoužil z cache dokumentácie. Šifrované hodnoty option používajú {{ enc(additionalKey): "SupportVal" }}.
Pridanie štýlov
Zdieľaný vzhľad príkladov je v /app/modules/Examples/assets/css/examples.css a je dostupný ako /assets/modules/Examples/css/examples.css.
Vytvorenie layoutu
setView('forms2') vykreslí celý HTML dokument. Moduly môžu view obaliť aj pomocou {{ layout:name }}. Táto ukážka ponecháva view samostatný.
Konfigurácia trás
Router::get($pair($p . '/forms2'), 'Examples:Forms@index2!', Router::STATIC_ROUTE);
Router::post($pair($p . '/forms2'), 'Examples:Forms@submit2!', Router::STATIC_ROUTE);
Tieto riadky zaregistrujte v initialize($dotApp) vedľa trás pomenovaných formulárov. $pair vráti obe varianty s lomkou aj bez lomky.
Úprava kontroléra
Metódy sú public static a prijímajú iba $request. Použite Renderer::new(). Keď volací reťazec končí na !, metóda nedostáva injektované služby ako ďalšie argumenty.
public static function index2($request)
{
$items = [
['value' => 'ValueItem1', 'text' => 'Text item 1'],
['value' => 'ValueItem2', 'text' => 'Text item 2'],
['value' => 'ValueItem3', 'text' => 'Text item 3'],
];
return self::view('forms2', [
'title' => 'Secure form demo',
'docsUrl' => '/documentation/examples/secure-forms',
'items' => $items,
]);
}
public static function submit2($request)
{
$answer = ['code' => 403, 'body' => ['status' => 0, 'error' => 1, 'error_txt' => 'CRC check failed!', 'message' => 'CRC check failed!']];
if ($request->crcCheck()) {
$answer = $request->form(['POST'], 'CSRF', function ($request) {
$payload = $request->data(true)['data'] ?? [];
$categoryVal = Crypto::decrypt((string) ($payload['category'] ?? ''), 'additionalKey');
$foreachVal = Crypto::decrypt((string) ($payload['foreach'] ?? ''), 'additionalKey2');
if ($categoryVal === false || $foreachVal === false) {
return [
'code' => 403,
'body' => ['status' => 0, 'message' => 'Data manipulation detected!'],
];
}
return [
'code' => 200,
'body' => [
'status' => 1,
'text' => 'Category: ' . $categoryVal . ', Foreach: ' . $foreachVal,
'message' => 'Form submitted successfully.',
],
];
}, function () {
return ['code' => 403, 'body' => ['status' => 0, 'message' => 'Invalid signature']];
}, $request->getPath());
}
if (!is_array($answer) || !isset($answer['body'])) {
return DotApp::DotApp()->ajaxReply(['status' => 0, 'error' => 1, 'error_txt' => 'Invalid signature', 'message' => 'Invalid signature'], 403);
}
return DotApp::DotApp()->ajaxReply($answer['body'], $answer['code']);
}
crcCheck()sa spúšťa predform().- Názov formulára je
CSRF, pretože view používaformName(CSRF). - Callback chyby pri
form()je povinný. - Posledný argument
form()je$request->getPath(), aby sa šifrovaný handler zhodoval s POST URL. - AJAX odpovede idú cez
DotApp::DotApp()->ajaxReply($body, $code).
Implementácia JavaScriptu
Súbor živej ukážky: /app/modules/Examples/assets/js/forms2.js. Klientské API je $dotapp. Ak sa knižnica ešte načítava, počkajte na udalosť dotapp.
(function () {
var runMe = function ($dotapp) {
$dotapp()
.form("#example2")
.before(function (data, form) {
if ($dotapp(form).attr("blocked") == 1) return $dotapp().halt();
$dotapp(form).attr("blocked", "1");
$dotapp("#noteBtn").attr("loading", "true").attr("loader", "dots");
})
.after(function (data, response, form) {
var reply = $dotapp().parseReply(response);
if (reply && (reply.status == 1 || reply.error == 0)) {
$dotapp(".output").html("Form submitted successfully!<br>" + (reply.text || reply.message || ""));
} else if (reply && (reply.error_txt || reply.message)) {
$dotapp("#error-message").attr("hide", "false").html(reply.error_txt || reply.message);
}
$dotapp(form).attr("blocked", "0");
});
};
if (window.$dotapp) runMe(window.$dotapp);
else window.addEventListener("dotapp", function () { runMe(window.$dotapp); }, { once: true });
})();
Použitie fasády Crypto
Šifrovanie v šablóne a dešifrovanie v PHP musia používať ten istý extra kľúč. Neúspešné dešifrovanie vráti false — porovnávajte s === false a nikdy to nepovažujte za reťazec.
$categoryVal = Crypto::decrypt((string) ($payload['category'] ?? ''), 'additionalKey');
if ($categoryVal === false) {
// tampered or wrong key
}