Sichere Formulare mit dotapp.js
Live-Demo öffnen unter /documentation/examples/run/forms2.
Dieses Beispiel ist die Live-Seite Examples:Forms@index2! / @submit2!. Auswahlwerte werden im Template verschlüsselt. Der Browser sendet mit fo-rm, formName, CRC und dotapp.js. Die Seite wird nicht neu geladen.
Voraussetzungen
Beginnen Sie mit dem Beispiel zu benannten Formularen, sodass das Modul Examples und der Controller Forms bereits vorhanden sind.
Die View erstellen
Die Live-Demo verwendet eine eigenständige View unter /app/modules/Examples/views/forms2.view.php. Die Datei ist ein vollständiges 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>
Platzieren Sie {{ formName(CSRF) }} zwischen den <fo-rm>-Tags. Setzen Sie action auf den aktuellen Pfad (die Live-Demo verwendet $request->getPath()). Übergeben Sie denselben Pfad als letztes Argument von form(), damit der Handler übereinstimmt. Laden Sie /assets/dotapp/dotapp.js mit einem eindeutigen Query-Parameter bei jedem Seitenrender, damit das CSRF-Token der Sitzung nicht von einer zwischengespeicherten Dokumentationsseite wiederverwendet wird. Verschlüsselte Optionswerte verwenden {{ enc(additionalKey): "SupportVal" }}.
Styles hinzufügen
Das gemeinsame Erscheinungsbild der Beispiele liegt unter /app/modules/Examples/assets/css/examples.css und wird als /assets/modules/Examples/css/examples.css ausgeliefert.
Das Layout erstellen
setView('forms2') rendert das vollständige HTML-Dokument. Module können Views auch mit {{ layout:name }} umschließen. Diese Demo hält die View in sich abgeschlossen.
Routen konfigurieren
Router::get($pair($p . '/forms2'), 'Examples:Forms@index2!', Router::STATIC_ROUTE);
Router::post($pair($p . '/forms2'), 'Examples:Forms@submit2!', Router::STATIC_ROUTE);
Registrieren Sie diese Zeilen in initialize($dotApp) neben den Routen für benannte Formulare. $pair liefert beide Schrägstrich-Varianten.
Den Controller aktualisieren
Methoden sind public static und nehmen nur $request entgegen. Verwenden Sie Renderer::new(). Endet die Callable-Zeichenkette mit !, erhält die Methode keine injizierten Dienste als zusätzliche Argumente.
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()läuft vorform().- Der Formularname ist
CSRF, weil die ViewformName(CSRF)verwendet. - Der Fehler-Callback von
form()ist erforderlich. - Das letzte Argument von
form()ist$request->getPath(), damit der verschlüsselte Handler zur POST-URL passt. - AJAX-Antworten laufen über
DotApp::DotApp()->ajaxReply($body, $code).
JavaScript implementieren
Live-Datei: /app/modules/Examples/assets/js/forms2.js. Die Client-API ist $dotapp. Warten Sie auf das Ereignis dotapp, falls die Bibliothek noch geladen wird.
(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 });
})();
Die Crypto-Fassade verwenden
Template-Verschlüsselung und PHP-Entschlüsselung müssen denselben Zusatzschlüssel verwenden. Eine fehlgeschlagene Entschlüsselung gibt false zurück — vergleichen Sie mit === false und behandeln Sie den Wert niemals als Zeichenkette.
$categoryVal = Crypto::decrypt((string) ($payload['category'] ?? ''), 'additionalKey');
if ($categoryVal === false) {
// tampered or wrong key
}