Skip to content
Snippets Groups Projects
Commit ef98de6f authored by Vincent Seyller's avatar Vincent Seyller
Browse files

Merge branch 'master' of git.unistra.fr:LP2/lp2

parents 16bdedfd d4e308dd
No related merge requests found
{
"name": "user-server",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"express-jwt": "^6.0.0",
"jsonwebtoken": "^8.5.1"
}
}
const express = require("express");
const body_parser = require("body-parser");
const jsonwebtoken = require("jsonwebtoken");
const expressjwt = require("express-jwt");
const path = require("path");
const secret = "fze5EVvs:,;hsegFZEQGhtrh,;$:^fz";
const app = express();
// app.use(express.static("../build"));
app.use(body_parser.json());
app.all("*", (req, res, next) => {
res.set("Access-Control-Allow-Origin", "*");
res.set("Access-Control-Allow-Credentials", true);
res.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, DELETE, PUT");
res.set(
"Access-Control-Allow-Headers",
"X-Requested-With, Content-Type, Authorization"
);
if (req.method === "OPTIONS") {
return res.status(200).end();
} else {
next();
}
});
const users = [
{
id: 1,
username: "pierre",
password: "kraemer"
},
{
id: 2,
username: "toto",
password: "tutu"
}
];
let nextid = 3;
app.post("/signin", (req, res, next) => {
const user = users.find(u => u.username === req.body.username);
if (user?.password === req.body.password) {
const token = jsonwebtoken.sign({ id: user.id }, secret, {
algorithm: 'HS256',
expiresIn: 60 * 60 * 12
});
const { password, ...user_without_pw } = user;
return res.json({
user: user_without_pw,
token
});
}
next({ status: 401, message: "Bad username or password" });
});
app.get("/whoami", expressjwt({ secret, algorithms: ['HS256'] }), (req, res, next) => {
const user = users.find(u => u.id === req.user.id);
if (user) {
const { password, ...user_without_pw } = user;
return res.json(user_without_pw);
}
next({ status: 404, message: "User not found" });
});
app.post("/signup", (req, res, next) => {
const available = !users.some(u => u.username === req.body.username);
if (available) {
users.push({
id: nextid++,
username: req.body.username,
password: req.body.password
});
return res.status(200).end();
}
next(new Error("Username not available"));
});
// app.all("/*", (req, res) => {
// res.sendFile(path.resolve("../build/index.html"));
// });
app.use((err, req, res, next) => {
if (err.status) {
return res.status(err.status).send(err.message);
} else {
return res.status(500).send(err.message);
}
});
const server = app.listen(4200, "localhost", () => {
const host = server.address().address;
const port = server.address().port;
console.log("App listening at http://%s:%s", host, port);
});
......@@ -37,7 +37,7 @@ Comme pour la liste de personnages, en attendant la réponse du serveur, le comp
S'assurer que la liste de films est bien mise à jour lors de la sélection d'un nouveau personnage.
V3
<!-- V3
---
Ecrire un custom hook `useDataFromUrl`.
......@@ -47,13 +47,13 @@ En interne, cette fonction déclare les éléments de `state` nécessaire, et d
Utiliser ce custom hook dans l'ensemble des composants qui font des requêtes.
Comment faire pour que l'on puisse passer un tableau d'URL à la fonction `useDataFromUrl`, et que le champ `data` obtenu soit un tableau contenant les données obtenue depuis chaque URL ?
Comment faire pour que l'on puisse passer un tableau d'URL à la fonction `useDataFromUrl`, et que le champ `data` obtenu soit un tableau contenant les données obtenue depuis chaque URL ? -->
V4
V3
---
Utiliser la bibliothèque [react-query](https://react-query.tanstack.com/) pour gérer les requêtes à l'API (à la place de notre custom hook).
Bien lire la documentation sur les requêtes (https://react-query.tanstack.com/docs/guides/queries) et s'assurer d'avoir compris le fonctionnement des `keys` et le passage de paramètres à la fonction asynchrone de récupération des données.
Bien lire la documentation sur les requêtes (https://react-query.tanstack.com/docs/guides/queries) et s'assurer d'avoir compris le fonctionnement des `keys`.
> ___Indication___ : pour récupérer un tableau d'identifiants de films à partir d'un tableau d'URL du type `https://swapi.dev/api/films/3` -> `const filmsId = filmsUrl.map(u => u.split('/').filter(Boolean).pop());`.
......
Authentification, routage et contextes
===
Ecrire une application qui gère la connexion d'un utilisateur en lien avec une API HTTP distante.
Lire la [doc de `react-router`](https://reacttraining.com/react-router/web/guides/quick-start).
V1
---
L'application affiche un menu (donné par un composant `Menu`) contenant les entrées suivantes :
- Home (qui mène à la route "/")
- si l'utilisateur n'est pas connecté :
- Signin (qui mène à la route "/signin")
- Signup (qui mène à la route "/signout")
- si l'utilisateur est connecté :
- le nom de l'utilisateur connecté ("Connected as ...")
- Signout (qui appelle la logique de déconnexion)
A chaque route ("/", "/signin", "/signup") correspond le rendu d'un composant particulier (`Home`, `Signin`, `Signup`).
Les informations relatives à l'utilisateur connecté sont maintenues dans le composant principal (seul à contenir la logique de connexion ainsi que la connaissance de l'API) :
- Un objet `user` dans le state (initialisé à `null`) contenant l'utilisateur actuellement connecté
- Une fonction `signin` qui permet de soumettre un couple (identifiant, mot de passe) à l'API distante qui, en cas de succès, renvoie un JSON Web Token (JWT) ainsi qu'un objet correspondant au `user` authentifié. Le token doit alors être stocké en `localStorage` et l'utilisateur peut être emmené vers la route "/".
- Une fonction `signup` qui permet de soumettre un couple (identifiant, mot de passe) pour inscription. En cas de succès, on emmène l'utilisateur vers la route "/signin".
Les composants `Signin` et `Signup` proposent un formulaire adéquat et font appel, lors de la soumission du formulaire, à une fonction `onSubmit` reçue en prop qui fait à son tour appel respectivement aux fonctions `signin` et `signup` du composant principal.
Faire en sorte qu'en cas d'échec des requêtes, le message d'erreur reçu s'affiche sous le formulaire.
Au lancement de l'application, faire en sorte de restaurer l'utilisateur connecté en exploitant le JSON Web Token présent en `localStorage` si il existe (voir la route `/whoami` de l'API).
En attendant la réponse du serveur, ne rendre qu'un message d'attente.
API
---
L'API HTTP fournie comprend les routes suivantes :
- `POST /signin` : reçoit un objet de la forme `{ username: '...', password: '...' }`. En cas de succès, renvoie une réponse HTTP code 200 avec un objet de la forme `{ token: '...', user: { id: 42, username: '...' } }`. En cas d'échec, renvoie une réponse HTTP code 401 avec un message d'erreur.
- `POST /signup` : reçoit un objet de la forme `{ username: '...', password: '...' }`. En cas de succès, renvoie une réponse HTTP code 200. En cas d'échec, renvoie une réponse HTTP code 500 avec un message d'erreur.
- `GET /whoami` : lit l'en-tête `Authorization` de la requête HTTP au format `"Bearer [JSON Web Token]"`. En cas de succès du décodage du token, renvoie une réponse HTTP code 200 avec un objet de la forme `{ id: 42, username: '...' }`. En cas d'échec, renvoie une réponse HTTP code 404 avec un message d'erreur.
V2
---
Les données concernant l'utilisateur connecté sont potentiellement utiles à différents niveaux de l'application.
Pour éviter d'avoir à passer ces informations manuellement au travers de nombreuses couches de composants ("props drilling"), on peut mettre en place un contexte, qui donnera accès aux données souhaitées dans tout le sous-arbre de l'application.
Dans un module séparé (`auth.js` par exemple), créer un contexte `AuthContext`, et exporter 2 éléments :
- un composant `AuthProvider` dans lequel on va déplacer les données et la logique liées à la gestion de l'utilisateur. Ce composant rend le `Provider` du contexte `AuthContext` en lui passant comme valeur un objet contenant les champs `user`, `signup`, `signin` et `signout`. Le contenu du `Provider` est les éléments fils reçus par le composant `AuthProvider` (`children`).
- un custom hook `useAuth` qui retourne simplement le résultat de `useContext(AuthContext)`
Nettoyer le composant principal de l'application qui ne devrait plus contenir que la déclaration du `AuthProvider`, le menu et les routes.
Dans les composants `Menu`, `Signin` et `Signup`, récupérer les données nécessaires du contexte d'authentification grâce à la fonction `useAuth`.
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment