Prejsť na obsah

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 pred form().
  • Názov formulára je CSRF, pretože view používa formName(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
}
        

Živá ukážka

/documentation/examples/run/forms2