Cookies afschermen: een formulier bij de server
Het sessie-id uit Onthouden op de server: sessies is de sleutel tot een sessie: wie het heeft, is voor je server ingelogd als die bezoeker, zonder wachtwoord. In deze reeks scherm je de cookie met dat id af. Eerst zet je een server klaar waarop je met je browser kunt inloggen.
Maak voor deze reeks een nieuwe map veiligheid-cookies, naast je eigen
project, en open die in VS Code. De server, de scripts en de databases van deze reeks komen daarin. Zo blijft je eigen gastenboek heel. Aan
het eind van de reeks pas je het geleerde toe op je eigen project.
Maak in die map een virtual environment en installeer de packages; hoe dat gaat staat bij Een map per reeks.
Een nieuwe map geeft geen nieuwe cookies. Een cookie hoort bij het adres
127.0.0.1, niet bij de map of de poort, dus de cookies van eerdere projecten,
zoals naam en volgorde, staan er ook nog. Verwijder ze in het tabblad
App, of werk in deze reeks in een privévenster.
Kijk alleen naar de cookies van je eigen server op 127.0.0.1, in je eigen
browser. Het sessie-id van een ander gebruiken om als die persoon binnen te
komen, is strafbaar.
Een formulier bij de server
Neem de server uit Wie mag wat, in de stand van
les 5: elk endpoint apart, met Alles wissen erin. Zet
er formulieren bij, als los bestand static/formulier.html: een om te
registreren, een om in te loggen en een voor een bericht.
<!doctype html>
<html>
<body>
<form method="post" action="/registreer">
<input type="text" name="naam" placeholder="Je naam" required>
<input type="password" name="wachtwoord" placeholder="Wachtwoord" required>
<button type="submit">Registreer</button>
</form>
<form method="post" action="/inloggen">
<input type="text" name="naam" placeholder="Je naam" required>
<input type="password" name="wachtwoord" placeholder="Wachtwoord" required>
<button type="submit">Inloggen</button>
</form>
<form method="post" action="/bericht">
<input type="text" name="bericht" placeholder="Je bericht" required>
<button type="submit">Verstuur</button>
</form>
</body>
</html>
In main.py komen er twee regels bij, zodat de server de map static laat
zien:
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="static"), name="static")
Zet de import bovenaan bij de andere, en app.mount direct onder
app = FastAPI().
Zo ziet je main.py er nu uit
Heb je de server uit Wie mag wat niet meer, of wil je zeker weten dat je
dezelfde hebt: dit is het hele main.py, met de twee nieuwe regels erin.
import secrets
import time
from fastapi import Cookie, FastAPI, Form, HTTPException
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from sqlitedict import SqliteDict
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.post("/registreer")
async def registreer(naam: str = Form(...), wachtwoord: str = Form(...)):
with SqliteDict("gebruikers.db") as gebruikers:
if naam in gebruikers:
raise HTTPException(status_code=400, detail="Deze naam is al bezet")
gebruikers[naam] = wachtwoord
gebruikers.commit()
return {"bericht": f"Welkom, {naam}"}
@app.post("/inloggen")
async def inloggen(naam: str = Form(...), wachtwoord: str = Form(...)):
with SqliteDict("gebruikers.db") as gebruikers:
opgeslagen = gebruikers.get(naam)
if opgeslagen != wachtwoord:
raise HTTPException(status_code=401, detail="Naam of wachtwoord klopt niet")
sessie_id = secrets.token_hex(16)
with SqliteDict("sessies.db") as sessies:
sessies[sessie_id] = {"naam": naam}
sessies.commit()
antwoord = JSONResponse({"bericht": f"Ingelogd als {naam}"})
antwoord.set_cookie(key="sessie_id", value=sessie_id, max_age=60 * 60 * 24 * 30)
return antwoord
@app.post("/bericht")
async def bericht_plaatsen(bericht: str = Form(...), sessie_id: str = Cookie(default="")):
with SqliteDict("sessies.db") as sessies:
mijn = sessies.get(sessie_id, {})
if not mijn:
raise HTTPException(status_code=401, detail="Log eerst in")
sleutel = f"bericht_{time.time_ns()}"
with SqliteDict("gastenboek.db") as db:
db[sleutel] = {"naam": mijn["naam"], "bericht": bericht}
db.commit()
return {"sleutel": sleutel}
@app.get("/berichten")
async def berichten():
with SqliteDict("gastenboek.db") as db:
return dict(db)
@app.delete("/bericht/{sleutel}")
async def bericht_verwijderen(sleutel: str, sessie_id: str = Cookie(default="")):
with SqliteDict("sessies.db") as sessies:
mijn = sessies.get(sessie_id, {})
with SqliteDict("gastenboek.db") as db:
bericht = db.get(sleutel)
if bericht is None or bericht["naam"] != mijn.get("naam"):
raise HTTPException(status_code=403, detail="Dit is niet jouw bericht")
del db[sleutel]
db.commit()
return {"bericht": "Verwijderd"}
@app.delete("/berichten")
async def alles_wissen(sessie_id: str = Cookie(default="")):
with SqliteDict("sessies.db") as sessies:
mijn = sessies.get(sessie_id, {})
with SqliteDict("gastenboek.db") as db:
van_mij = []
for sleutel, bericht in db.items():
if bericht["naam"] == mijn.get("naam"):
van_mij.append(sleutel)
for sleutel in van_mij:
del db[sleutel]
db.commit()
return {"bericht": "Jouw berichten zijn gewist"}
Start de server in je map veiligheid-cookies met fastapi dev main.py.
Er gaat iets mis
RuntimeError: Directory 'static' does not exist
fastapi dev stopt meteen, en de foutmelding eindigt met die regel.
Oorzaak: er is nog geen map static naast main.py, of hij heet anders.
app.mount heeft die map nodig om te kunnen starten.
Oplossing: maak de map static in veiligheid-cookies, zet
formulier.html erin, en start de server opnieuw.
Not Found
Op http://127.0.0.1:8000/static/formulier.html staat {"detail":"Not Found"}.
Oorzaak: het bestand staat niet in de map static, of het heet anders,
bijvoorbeeld formulier.html.txt of Formulier.html.
Oplossing: zet formulier.html in de map static, met precies die naam.
Opdrachten
Opdracht 1: Predict - Twee keer registreren
Je maakt met het eerste formulier nog een keer een account met de naam sara.
Vraag: wat toont je browser?
Antwoord
{"detail":"Deze naam is al bezet"}. registreer kijkt eerst of de naam al in
gebruikers.db staat, en weigert dan met 400. Zonder die controle zou de
tweede registratie het wachtwoord van de eerste overschrijven.
Door naar les 2: scripts lezen cookies.