Wachtwoorden: een server met accounts
Hier bouw je op verder: Tuples uitpakken
Weet je dit niet meer zeker? Lees het terug voordat je verdergaat.
- Python Tuples uitpakken
Maak voor deze reeks een nieuwe map veiligheid-wachtwoorden, naast je eigen
project, en open die in VS Code. De server, de scripts en de database 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.
Je zet de server van deze reeks neer en maakt met een script drie accounts aan.
Je gastenboek heeft accounts sinds Registreren: een account opslaan, en controleert het wachtwoord sinds Inloggen: een wachtwoord controleren. Het bewaart dat wachtwoord precies zoals de gebruiker het intypt; in Registreren zag je het al staan. Deze reeks laat zien waarom dat gevaarlijk is, en maakt het stap voor stap veilig.
Het startbestand
Je werkt met dezelfde twee stappen, als een kleine server die JSON teruggeeft,
zodat een script elk antwoord kan lezen: /registreer uit de basis, en
/inloggen met de controle uit je gastenboek, als eigen endpoint zoals in
Sessies, maar nog zonder sessie. Een fout wachtwoord
geeft hier geen 401 maar een melding: het gaat in deze reeks om wat er in de
database staat. Zet dit in main.py:
from fastapi import FastAPI, Form, HTTPException
from sqlitedict import SqliteDict
app = FastAPI()
@app.post("/registreer")
async def registreer(naam: str = Form(...), wachtwoord: str = Form(...)):
with SqliteDict("gebruikers.db") as db:
if naam in db:
raise HTTPException(status_code=400, detail="Deze naam is al bezet")
db[naam] = wachtwoord
db.commit()
return {"bericht": f"Welkom, {naam}"}
@app.post("/inloggen")
async def inloggen(naam: str = Form(...), wachtwoord: str = Form(...)):
with SqliteDict("gebruikers.db") as db:
if db.get(naam) == wachtwoord:
return {"bericht": "Ingelogd"}
return {"bericht": "Naam of wachtwoord klopt niet"}
- Regel 9-10:
Bestaat de naam al, dan weigert de server hem met een 400. Zonder die controle overschrijft iemand die zich als
sararegistreert het wachtwoord van Sara, en kan hij daarna als haar inloggen. - Regel 11:
Het wachtwoord gaat letterlijk de database in, onder de naam van de gebruiker.
- Regel 18:
Inloggen vergelijkt wat de gebruiker intypt met wat er in de database staat. Staat de naam er niet in, dan geeft
db.getNone, en dat is nooit gelijk aan een wachtwoord.
Start de server met fastapi dev main.py.
Drie accounts aanmaken
Doe dit uitsluitend met je eigen server op 127.0.0.1 en je eigen
database. De wachtwoorden van een ander bekijken of gebruiken is strafbaar.
Maak naast main.py een los scriptje registreer.py dat drie gebruikers
aanmaakt, net zoals een formulier dat zou doen. Het gebruikt httpx, zoals in
Een script als bezoeker.
import httpx
adres = "http://127.0.0.1:8000/registreer"
gebruikers = [("sara", "welkom123"), ("alex", "Zonnebloem-Fiets-42"), ("noor", "welkom123")]
for naam, wachtwoord in gebruikers:
antwoord = httpx.post(adres, data={"naam": naam, "wachtwoord": wachtwoord})
print(antwoord.json())
- Regel 5:
Drie gebruikers, elk een tuple van naam en wachtwoord. Sara en Noor kozen hetzelfde wachtwoord.
- Regel 7:
httpx.poststuurt een formulier:datazijn de velden, net alsname="naam"enname="wachtwoord"in een HTML-formulier.
Draai het in een tweede terminal, in dezelfde map.
Zo ziet je main.py er nu uit
from fastapi import FastAPI, Form, HTTPException
from sqlitedict import SqliteDict
app = FastAPI()
@app.post("/registreer")
async def registreer(naam: str = Form(...), wachtwoord: str = Form(...)):
with SqliteDict("gebruikers.db") as db:
if naam in db:
raise HTTPException(status_code=400, detail="Deze naam is al bezet")
db[naam] = wachtwoord
db.commit()
return {"bericht": f"Welkom, {naam}"}
@app.post("/inloggen")
async def inloggen(naam: str = Form(...), wachtwoord: str = Form(...)):
with SqliteDict("gebruikers.db") as db:
if db.get(naam) == wachtwoord:
return {"bericht": "Ingelogd"}
return {"bericht": "Naam of wachtwoord klopt niet"}
Dit is de beginstand. In de volgende lessen verandert steeds één stuk.
Er gaat iets mis
Deze naam is al bezet
Je draait registreer.py een tweede keer, en krijgt:
{'detail': 'Deze naam is al bezet'}
{'detail': 'Deze naam is al bezet'}
{'detail': 'Deze naam is al bezet'}
Oorzaak: de eerste keer heeft je server de drie accounts al opgeslagen in
gebruikers.db. Een tweede registratie met dezelfde naam weigert hij met een
400.
Oplossing: niets, je accounts staan er al. Wil je opnieuw beginnen, gooi
dan gebruikers.db weg en draai het script opnieuw. Je server mag blijven
draaien: hij opent het bestand alleen tijdens een verzoek, en maakt bij het
eerste verzoek een nieuw, leeg bestand.
Opdrachten
Opdracht 1: Modify - Inloggen met een script
Maak een kopie van registreer.py die inlogt in plaats van registreert: één
keer als Sara met welkom123, en één keer als Sara met welkom124.
Tip
Verander het adres naar /inloggen, en zet in de lijst twee pogingen voor
Sara.
Antwoord
import httpx
adres = "http://127.0.0.1:8000/inloggen"
pogingen = [("sara", "welkom123"), ("sara", "welkom124")]
for naam, wachtwoord in pogingen:
antwoord = httpx.post(adres, data={"naam": naam, "wachtwoord": wachtwoord})
print(antwoord.json())
Uitvoer:
{'bericht': 'Ingelogd'}
{'bericht': 'Naam of wachtwoord klopt niet'}
Inloggen verandert niets in de database, dus je kunt dit script zo vaak draaien als je wilt.
Door naar les 2: waarom geen gewone tekst.