Bevezetés
Továbbra is az űrlapokat vizsgáljuk, hiszen
ezeken keresztül tudunk a legkönnyebben kapcsolatba kerülni a
felhasználóinkkal. Én most külön Javascript oktatást nem szeretnék
tartani a blogomban, de több egyetemi kollégám készített a nyelvről
és alkalmazásáról egy elég jó kezdeti összefoglalót, amit szívesen
megosztok itt veletek: http://webprogramozas.inf.elte.hu/tananyag/kliens/ és ez szintén hasznos lehet: http://webprogramozas.inf.elte.hu/tananyag/weaf1/
A
HTML5-ös űrlap bemeneti elemeknél végzett megszorítások segítenek a
felhasználóknak megfelelően kitölteni egy mezőt, azonban nem védenek a
támadások ellen. Induljunk ki onnan, hogy valaki "megpróbálja feltörni" a HTML5-ös validációnkat,
tehát mint ahogy az előző bejegyzésben is írtam, élőben belenyúl a
kódba és kitörli mondjuk a "required" attribútumot a HTML tag-ünkből,
majd úgy küldi el az űrlapot, hogy nem tölti ki a kötelezően kitöltendő
mezőt. Mit tehetünk még ekkor a validációnk sikerességéért?
Javascript-et
fogunk használni, ami persze még közel sem jelent majd számunkra
tökéletes védelmet (olyan nincs is), de még egy fokkal nehezebbé tesszük
az alkalmazásunk feltörését.
Ellenőrzés Javascript-tel
Ismét
a komplex példánkat fogjuk elővenni és már a gyakorlatban használni
fogjuk a Javascript validációs képességeit. Legelőször a
Légitársaságokat (airlines) létrehozó űrlapon fogunk tevékenykedni,
ehhez azonban biztosítsuk a helyet a script-jeinknek. A layout.blade.php
fájlban a body záró tag-je elé helyezzük el ezt:
@stack('scripts')
Magyarázat és tipp: Nagyon hasonlóan a @yield - @section Blade direktíva pároshoz, itt a @stack - @push
lesz a segítségünkre abban, hogy Javascript kódokat helyezzünk el az
egyes gyermekoldalakon (a layout szülő nézetünket így nem kell
teleszemetelnünk). A különbség a két páros között, hogy amíg egy
gyerekoldalon egy yield-be csak egyszer tudunk kódot írni a section-nel,
addig egy stack-be
bármennyiszer szúrhatunk be (push-olhatunk a verembe) akár újabb és
újabb kódokat, amennyit csak szeretnénk. Míg a yield-section párost
inkább a HTML kódokhoz és a CSS/Javascript csomagok, osztálykönyvtárak
importálására használjuk, addig a stack-push párost a változó CSS és
főleg Javascript kódjaink beszúrására alkalmazzuk. A saját Javascript
kódjainknak fenntartott stack-et pedig azért helyezzük el a body záró
tag-je elé, mert a kódjainkat gyakran használjuk az oldalon
megjelenített (render-elt) HTML kód manipulálására és ha akkor
szeretnénk megváltoztatni valamelyik HTML elemet, amikor még nem került
megjelenítésére, akkor hibát kapnánk a Javascript Console-ban, hogy még
nem talált ilyen elemet a DOM fában... hmm... érzem azért, hogy
használtam ebben a bekezdésben olyan kifejezéseket, amelyek nem biztos,
hogy mindenki számára ismertek. Emiatt tényleg azt javaslom, hogy ha
valami nem világos ezek közül, akkor érdemes megnézni ezt az oldalt és többminden is egyből világossá válik.
Megvan
tehát a szülő layout nézetünkben a Javascript kódjaink helye, nincs más
hátra, mint az utód nézet fájlba beillesszünk saját kódokat, amivel
ellenőrizzük az űrlapunkat.
name="legitarsasagLetrehozo"
onSubmit="return validateForm()"
A form nyitó tag-jébe
helyezzük el az iménti két új attribútumot. Az egyikkel nevet adunk az
űrlapnak, míg az onsubmit attribútummal jelezzük majd az űrlapnak, hogy
amikor a felhasználó rákattint a Mentés (Küldés) gombra, akkor milyen
metódusnak kell végrehajtódnia a tényleges adatelküldés előtt. Hozzuk is
létre és szúrjuk be a stack-be az új függvényünket (bár nincs
jelentősége, én a @endsection után helyezem el a kódomat, de
megtehetném, hogy a @section elé tenném be...):
@push('scripts')
<script>
function validateForm() {
let x = document.forms["legitarsasagLetrehozo"]["legitarsasagneve"].value;
if (x == "") {
alert("A nevet kötelező kitölteni");
return false;
}
}
</script>
@endpush
Teszteljünk! Azonban egy kicsit könnyítsük meg a
dolgunkat és ne csak "hackeljünk" azzal, hogy valós időben
átszerkesztjük a form elemeit és kivesszük az input mezők required
attribútumát. A tesztelés meggyorsítása miatt a create nézetet
szerkesztve vegyük ki ezt a required attribútumot mindkét bemeneti elem
nyitótag-jéből.

Ez
így egész jó kiindulásnak. Most még azt csináljuk meg, hogy ne lehessen
létrehozni olyan légitársaságot, aminek nincsen telephelye... legalább 1
telephelye minden új légitársaságnak lennie kell, amit így szabályként
meg is fogalmaztunk.
if (document.forms["legitarsasagLetrehozo"]["cities[]"].selectedOptions.length < 1) {
alert("Legalább 1 telephelyet állítson be");
return false;
}
Adjuk hozzá az
iménti feltételvizsgálatot a validateForm() függvényünkhöz. Itt a
feltételvizsgálat csak akkor lesz igaz, hogy ha kevesebb mint 1
kiválasztott elem van a listában, persze ha azt szeretném megkövetelni,
hogy több kiválasztott elemnek is lennie kell, mondjuk minimum 3
telephellyel kell rendelkeznie az új légitársaságnak, akkor azt is
beállíthatnánk itt, minden csak tőlünk és a meghatározó szabályainktól
függ. Egy oldalfrissítés után pedig teszteljük is le:

Látható,
hogy a nevet megadtuk, de nem választottunk ki egy telephelyet sem,
emiatt vissza is kaptuk a figyelmeztetést és az űrlap nem lett elküldve a
szervernek. Ez persze nem a legszebb megoldás, pusztán csak a működést
szerettem volna ábrázolni így.
Constraint Validation API
A legtöbb böngésző már támogatja a címben írt API (Application Programming Interface). Így például a gombokhoz, bemeneti mezőkhöz (input, textarea), select-hez
van már egy olyan felület, amit alapértelmezetten használhatunk
validációra és nem kell különböző egyéb Javascript osztályokat
importálnunk. Minden imént felsorolt elem automatikusan rendelkezik
azzal a lehetőséggel, hogy validáljuk őket (ellenőrizhetjük a
következőket: illeszkedik-e a bemenet egy mintára, túl rövid/hosszú-e a
bemenet, túllép/nem ér el valamilyen limitet a szám bemenet értéke,
típusprobléma e-mailek vagy URL-ek kapcsán, hiányzó bemenet, vagy
egyszerűen csak valamilyen egyéb, általunk definiált szabálynak nem
felel meg a bemenet). Ezeket tehát mind támogatja az API, illetve ezek
ellenőrzését. Az API még azt is lehetővé teszi, hogy az imént felsorolt
problémák ellenőrzésénél különböző hibaüzeneteket állíthassunk be a
setCustomValidity(message) metódus segítségével. Az itt felsoroltakat
kipróbáljuk a gyakorlatban is és megnézzük, hogy hogyan működnek, de
előtte csinálunk a validateForm() függvényünkbe egy sima kiíratást.
console.log(document.forms["legitarsasagLetrehozo"]["cities[]"]);
Ekkor tudjuk ellenőrizni az általam leírtakat a böngésző Javascript
Console-jában:

A validationMessage tartalmazza a hibaüzenetet, ha nem stimmel valamilyen ellenőrzés. A validity attribútum tartalmaz egy ValidityState objektumot, aminek látható a felsorolásában azok a dolgok, amiket az imént én is felsoroltam részletesen, amilyen problémák előfordulhatnak egy ilyen select-tel. A willValidate beállítás pedig azt jelzi nekünk, hogy ellenőrzésre fog-e kerülni a vizsgált (kiíratott) űrlapelem.
A további gyakorláshoz térjünk vissza az utasok létrehozó űrlapjához, mert ott az előző bejegyzésben sok szabályt állítottunk be, amelyeket most a Javascript és a Constraint Validation API segítségével is ellenőrizni tudunk majd. Bővítsük a passengers / create.blade.php -t az @endsection után:
@push('scripts')
<script>
const utasneve = document.getElementById("utasneve");
utasneve.addEventListener("input", function(event) {
if (utasneve.validity.tooLong || utasneve.validity.tooShort) {
utasneve.setCustomValidity("A névnek legalább 10 és legfeljebb 50 karakteresnek kell lennie!");
utasneve.reportValidity();
} else {
utasneve.setCustomValidity("");
}
});
const age = document.getElementById("age");
age.addEventListener("input", function(event) {
if (age.validity.rangeOverflow || age.validity.rangeUnderflow) {
age.setCustomValidity("Legalább 6 és legfeljebb 99 éves lehet az utas!");
age.reportValidity();
} else {
age.setCustomValidity("");
}
});
const email = document.getElementById("email");
email.addEventListener("input", function(event) {
if (email.validity.typeMismatch) {
email.setCustomValidity("Ide egy helyes e-mail címet várok!");
email.reportValidity();
} else {
email.setCustomValidity("");
}
});
const phone = document.getElementById("phone");
phone.addEventListener("input", function(event) {
if (phone.validity.patternMismatch) {
phone.setCustomValidity("Kövesse a mintát!");
phone.reportValidity();
} else {
phone.setCustomValidity("");
}
});
const repulojarata = document.getElementById("repulojarata");
repulojarata.addEventListener("input", function(event) {
if (repulojarata.validity.valueMissing) {
repulojarata.setCustomValidity("Válasszon ki egyet!");
repulojarata.reportValidity();
} else {
repulojarata.setCustomValidity("");
}
});
</script>
@endpush
Magyarázat: Ahogy az látható is a kódsorozatban, mindenféle ellenőrzést végrehajtunk: maxlength és minlength értékeket a tooLong és tooShort párossal, max és min értékeket a rangerOverflow és rangerUnderflow párossal, email típusú bemeneti mezőt typeMismatch -csel, a telefonszám mezőnél a megadott mintát (patternMismatch -csel) ellenőrizzük, míg végül a repülőjáratnál a hiányzó érték (valueMissing) esetén írjuk ki, hogy ki kell választani egyet. Teszteljük is le!

Minden billentyűlenyomásnál megjelenik a Javascript-tel ellenőrzött mezőnél a saját szövegezésű hibaüzenetünk, amíg nem lesz érvényes a bemeneti mező.
Támadás a Javascript-es validáció ellen
Hogyan lehetne ezt megtámadni? Hát ez egy jó kérdés, de induljunk ki abból, hogy a támadó hogyan tudja megnézni a Javascript kódunkat, ami ellenőrzi a bemeneti mezőinket. Firefox-ban a Debugger fülön tudja megnézni a kódot, így:

Innentől kezdve ő már tudja, hogy a getElementById-val kérjük le a mezőt, tehát meg tudja támadni a kódunkat, például így:
- Átírja a "megtámadni kívánt" mező id attribútumát, ahogy az előző bejegyzésben már láttuk, és onnantól kezdve már nem vonatkozik rá a javascript-es ellenőrzés
- Mondjuk elküldi (elmenti) úgy az űrlapot, hogy megad mondjuk egy 200 éves kort magának, amit mi ugye nem szerettünk volna, hogy előfordulhasson...
Ez persze csak egy "butácska példa", de annyi látható belőle, hogy ha már ismeri a kódunkat, ami kliens oldalon van, tehát a támadó böngészőjében, akkor onnantól ő már, ha elég dörzsölt, akkor ki tudja cselezni a mi kliens oldali validációs szabályainkat...
Validáció-specifikus Javascript keretrendszerek, osztálykönyvtárak
Ahogy a CSS kapcsán megismerhettük, hogy lehet, sőt érdemes keretrendszereket használni, úgy mint a Bootstrap vagy a Tailwind
rendszereket, itt is erről van szó. Javascript-es osztálykönyvtárakkal
és keretrendszerekkel igazából Dunát lehetne rekeszteni, kicsit talán
nehéz is eligazodni köztük. Én most kigyűjtöttem néhányat, amik űrlapok
ellenőrzésére szolgálnak leginkább. Abban az ügyben, hogy melyik a jobb /
rosszabb, én biztosan nem foglalnék állást, érdemes kipróbálni 1-2-t
közülük és ami jobban "kézre áll", azt lehet utána alkalmazni.
Általában elmondható, hogy az alap funkcionalitásokra, ellenőrzésekre
mindegyik alkalmas, ha pedig valamelyik fejlesztői kitalálnak valami
egyedit, jót, akkor azt utána gyorsan lemásolják a többi fejlesztői is.
Itt egy lista, direkt nem számozott sorrendben, mert inkább utána
sorolok fel szempontokat, hogy mi szerint érdemes választani ezek vagy
éppen más hasonlók közül:
Néhány szempont, ami alapján érdemes választani közülük:
- Mivel
egy nagy alkalmazásnál biztosan sok JS csomagot használunk, érdemes
figyelni a validációs csomag méretére (minél kisebb legyen).
- Mikor
frissítették utoljára: ha már régebben érkezett hozzá frissítés, akkor
nem biztos, hogy érdemes ezt használni, mert esetleg a fejlesztői már
nem gondozzák tovább, ami problémás működéseket szülhet.
- Nézzünk
meg példákat, próbáljuk ki a "szimpatikusakat", hogy mennyire áll
nekünk kézre az alkalmazásuk, mert hogy ha nem logikus számunkra, vagy
nehezen használható, akkor annak az alkalmazása a későbbiekben csak
nyűggel jár majd.
- Van-e hozzá weboldal: dokumentáció, mintapéldák stb.
- Mennyi
és milyen további függőségeket tartalmaz még. Nyilván minél kevesebb
egyéb függőséget tartalmaz, annál inkább jobb lehet nekünk (kisebb
eséllyel lesz problémás a használata egyéb függő csomagok miatt).
A következő bejegyzésben ezek közül kiválasztok egyet-kettőt és megmutatom, hogy hogyan is kell használni őket a gyakorlatban.
Miután
végrehajtottuk a Javascript-es teszteléseinket, érdemes visszatenni a
kódjainkba a HTML5-ös attribútumokat, amelyek az ellenőrzést segítették,
így újra - bár csak kliens oldalon vagyunk továbbra is -, de kétlépcsős
lesz az űrlapunk ellenőrzése.
Ennek a bejegyzésnek a Github commit-je itt érhető el. Tipp: mivel a kódokat formáztam is a VSCode-ban az ALT + SHIFT + f billentyűparanccsal, ezért a kódokban az egyes tag-ek attribútumait új sorba kezdte, ez egy formázási beállítás miatt van, de amikor sok attribútuma van már egy tag-nek, akkor így jobban át is lehet látni a dolgokat.
Most megcsináltuk az airlines és a passengers mappában lévő két create nézeteket. Gyakorlásként a hozzájuk kapcsolódó két edit nézetet is érdemes ugyanígy megvalósítani.