TDDD83 Kandidatprojekt datateknik
Laboration 3
# Koppla samman front end och back end
Vi ska i denna labb koppla ihop er front end med er back end.
Ni ska även få implementera ett enklare autentiserings- och
behörighetssystem. Med hjälp av autentisering ser vi till att
endast inloggade användare får tillgång till datan vi vill
skydda. Med behörighetssystemet ser vi till att olika användare
har olika behörigheter, som ger dem olika rättigheter att
komma åt och manipulera datan.
__AJAX__:
AJAX möjliggör att hämta och skicka data även efter att en webbsida har laddats.
Detta sker med hjälp av Javascript. [Läs en introduktion här](https://www.w3schools.com/js/js_ajax_intro.asp).
Observera att exemplet som visas ej använder jQuery, och ser därför annurlunda
ut än när ni använder jQuery för AJAX-anrop.
## Förberedande frågor
Svara på följande frågor och skriv ner era svar innan demonstration
1. Vad står AJAX för?
2. Skissa fram en lösning för hur er server skulle kunna få reda på vilken användare som skickar en viss förfrågan.
## Del 1: AJAX
Börja med att följa [instruktionerna om "Påbörjande av ny labb"](inlamning-och-git#p%E5b%F6rjande-av-ny-labb).
Vi börjar att ändra i nuvarnade implementation på klient-sidan
så att klienten hämtar data från er Flask-server (från labb 1) asynkront.
1. Ersätt serverStub med AJAX-anrop i `client.js` och ta bort `serverStub.js`, testa
om er nya implementation fungerar genom att ta bort filen
`serverStub.js` och se ifall allt fortfarande fungerar.
__Tips:__ Använd följande kodskiss för att ersätta anropet `serverStub.getCars()`.
För att ersätta de andra metoderna kan nedanstående exempel modifieras med avseende på url och type. Er servers address (ex. `http://localhost:5000`) bör sparas i en variabel för att enklare kunna ändras i framtiden.

Eftersom HTTP-metoden GET är så pass vanlig finns även en jQuery-metod, som är lite
enklare att använda, tillgänglig:

Observera att exemplen ovan gör samma sak. Fler metoder finns att hitta i [dokumentationen](https://api.jquery.com/category/ajax/).
Observera att alla anrop till servern ska vara asynkrona, d.v.s. `async: false` eller liknande metoder får __ej__ användas.
## Del 2: Användarmodellen
Vi ska bygga autentiseringssystem i Flask där vi sköter registrering, inloggning och behörighet till data.
Som ett första steg ska vi nu ändra lite i nuvarande implementation
så det blir mer anpassat för att hantera olika typer av användare.
1. Refaktorisera (döp om) er modell för `Customer` till `User` och
lägg till ett nytt fält, `is_admin`. Fältet ska vara av typen boolean och med standardvärdet `False`.
Det finns även andra lösningar för att skilja mellan olika typer av användare.
I denna laboration är dock denna lösning praktisk eftersom den i princip endast kräver att vi lägger till ett fält i databasen.
Som ni kan se har vi satt ett initialt värde för fältet `is_admin`, detta
är enbart för att säkerställa att nya användare alltid sätts som icke-admin.
Observera att ni även behöver uppdatera relationer och metoder på alla platser
ni använt `Customer`.
__Tips:__ Använd `Ctrl+Shift+F` i VSCode för att söka efter `customer` i hela mappen.
## Del 3: Registrering och lösenordshantering på server
Nästa steg i vår autentiseringsprocess är att ge användaren möjlighet att identifiera sig genom
att ange ett lösenord vid inloggning. En första idé för att lösa denna uppgift är att skapa en
kolumn i vår databas för att lagra användarens lösenord som en sträng. Men nu behöver vi se upp,
är det rimgligt att spara ett lösenord i plan text i en databas? Svaret är naturligtvis nej, nej
och åter nej. __Det är aldrig en bra idé att spara lösenord som plan text i en databas__. Vi löser
detta istället genom att använda en [hash-funktion](https://sv.wikipedia.org/wiki/Hashfunktion)
på lösenordet innan vi lagrar det i databasen.
I den här laborationen kommer vi fortsätta använda hjälpbibliotek precis som tidigare.
I detta fall ska vi använda bcrypt vilken är ett hashnings-bibliotek.
Läs igenom [bcrypt dokumentationen](https://flask-bcrypt.readthedocs.io/en/latest/) för att hitta
metoder som kan hjälpa oss att hasha lösenord innan de lagras i databasen.
När vi sedan ska validera huruvida en användare är autentisk kommer vi köra samma hash-funktion på
den hävdade användarens angivna lösenord och jämföra med det hashade lösenordet vi lagrat i databasen.
1. Installera `flask-bcrypt` i er utvecklingsmiljö och lägg in följande två rader
på lämpliga platser i er main.py:


2. Lägg nu till ett fält i er användar-modell för att spara ett hashat lösenord. Döp det till `password_hash`
och sätt typen till `String`. Sedan kan det vara smidigt att lägga till en metod i modellen som nedan för att enkelt kunna
hasha och sätta lösenord.

3. Skapa nu funktionalitet för routen `/sign-up`.
Ni ska ta emot JSON med en e-postadress, ett namn samt ett lösenord. Denna data ska användas för att spara en ny användare i databasen.
Lösenordet ska hashas innan det sparas. Servern ska sedan svara med statuskoden 200.
__Tips:__ Använd metoden `set_password` för att hasha lösenordet innan ni sparar det.
## Del 4: Autentisering med token
Den övergripande strukturen vi kommer följa är
token-baserad autentisering. Övergripande betyder det att token genereras
på servern vid inloggning. Detta token används sedan vid alla requests för att avgöra vem
användaren är (se nedanstående figur). Läs mer om token-baserad autentisering [här](https://jwt.io/introduction/).

Eftersom vem som helst kan använda Postman eller andra verktyg för att skicka godtycklig request till en webbserver räcker det inte med att användaren måste logga in på klienten - servern måste alltid skyddas mot alla tänkbara förfrågningar.
För att skapa och läsa tokens ska vi använda oss av paketet [Flask-Jwt-Extended](https://flask-jwt-extended.readthedocs.io/en/stable/).
Detta paket kommer att benämnas som _Flask-JWT_ framöver.
1. Installera Flask-JWT genom att köra kommandot
`(venv) $ pip install flask-jwt-extended`
eller genom att använda er eventuella requirements.txt.
2. Konfigurera Flask-JWT i `main.py` genom att:
* Importera `JWTManager` från `flask_jwt_extended`
* Sätt `app.config['JWT_SECRET_KEY']` till någon svårgissad sträng
* Initiera JWTManager: `jwt = JWTManager(app)`
3. Skapa funktionalitet för `POST /login`. Metoden ska ta emot en e-post och ett lösenord.
Kontrollera att de är korrekta och svara sedan med ett token och användardata (se till
att inte skicka med lösenordshashen). Svaret ska alltså se ut såhär:

Om inloggningsuppgifterna inte är korrekta ska servern svara med HTTP-statuskoden 401.
__Tips__: Använd [`create_access_token`](https://flask-jwt-extended.readthedocs.io/en/stable/api/#flask_jwt_extended.create_access_token) från Flask-JWT.
4. Servern måste också hålla koll på att rätt användare får tillgång till rätt funktionalitet och data.
Modifera er server-kod så att alla routes förutom `/sign-up` och `/login` kräver en autentiserad användare.
Om en användare försöker komma åt en route och inte kan autentiseras, ska servern svara med statuskoden 401.
Testa med Postman både med och utan auth-header.
__Tips:__ Använd docoratorn `@jwt_required` som i [dokumentationen](https://flask-jwt-extended.readthedocs.io/en/stable/basic_usage/)
I Postman kan ni skicka med ett token på detta vis:

## Del 5: Registrering, inloggning och utloggning på klient
1. Skapa registreringsmöjlihet:
1. Skapa menyalternativet "Registrera dig".
2. Skapa en ny vy innehållande ett HTML-formulär som tar in e-post, namn och lösenord.
3. Skapa funktionallitet för att skicka formulärets data till `/sign-up` så att en ny användare skapas.
4. När användaren har skapats, se till att välkomstvyn visas istället för formuläret.
2. Skapa inloggningsmöjlighet:
1. Skapa menyalternativet "Logga in".
2. Skapa en ny vy innehållande ett HTML-formulär som tar in e-post och lösenord.
3. Skapa funktionallitet för att skicka formulärets data till `/login` där uppgifterna ska valideras.
4. Spara det token och den användardata som servern svarar med efter lyckad inloggning.
__Tips__: [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) kan
användas för att spara data i webbkläsaren på följande sätt:

5. När användaren har lyckats logga in, se till att välkomstvyn visas.
__Tips__: Utvecklingsverktyget i webbläsaren kan användas för att läsa innehållet i sessionStorage.
Det går även att ta bort värden med hjälp av detta. Utvecklingsverktygen i Chrome öppnas genom att trycka på F12-tangenten.

3. Dölj menyalternativen "Logga in" och "Registrera dig" om det finns ett token sparat i webbläsaren.
Se även till att dessa menyalternativ visas om ett token inte finns sparat.
__Tips__: Använd jQuery-metoden `toggleClass()` och Bootstrap-klassen `d-none` för att kontrollera synligheten för ett element.
Denna logik kan med fördel placeras så att den körs när sidan laddas.

4. Skapa utloggningsmöjlighet:
1. Skapa menyalternativet Logga ut och visa denna enbart då en användare är
inloggad.
2. Ta bort token från `sessionStorage` då användaren loggat ut.
__Tips__: Använd `sessionStorage.removeItem()`
## Del 6: Autentisering med AJAX
1. Visa menyalternativet "Bilar" endast om användaren är inloggad.
2. Skicka det token som ni sparade vid inloggning i varje request som ska autentiseras. (ej
för `/login` och `/sign-up`). Det kan göras genom att skicka in följande
argument till `$.ajax`, `$.get` och så vidare.

3. Visa knapparna "Redigera" och "Ta bort" på varje bil endast om användaren är admin.
4. Skapa och visa en "Boka"-knapp på varje bil som visas om användaren __inte__ är admin.
Dock, om bilen redan är bokad, ska "Bokad av _namn_" visas istället för boka-knappen.
5. Skapa `POST /cars/[int:car_id]/booking` som sätter en relation mellan den
inloggade användaren och bilen ifall den inte redan är bokad. Servern ska svara med
`{success: true}` om bokningen gick igenom och `{success: false}` om den redan var bokad av någon annan. Gör så att en förfrågan till denna route skickas när användaren klickar på en bils bokningsknapp och att texten "Bokad av namn" visas om bokningen lyckades.
__Tips__: Använd [`get_jwt_identity`](https://flask-jwt-extended.readthedocs.io/en/stable/api/#flask_jwt_extended.get_jwt_identity)
för att hitta den inloggade användaren.
6. Skapa nu en lösning för att avboka bilar på motsvarande sätt som bilar bokas.
## Redovisning:
Följ [instruktionerna för "Redovisning"](inlamning-och-git#redovisning).
Sidansvarig: Martin Sjölund
Senast uppdaterad: 2021-01-18