Juste un projet perso' pour se familiariser avec d'autres technos en créant un petit prototype Uber, serveur et client.
Ce projet fera l'objet d'une série longue sur ma chaßne YouTube j'ai nommé "Let's Play".
Ci-dessous mes notes / idées permettant de structurer cette série de vidéos.
/!\ Je ne merge pas les pull requests pour le moment. :)
Courant juillet 2016, Uber a communiqué sur les technologies utilisées au sein de son service : https://eng.uber.com/tech-stack-part-one/ https://eng.uber.com/tech-stack-part-two/
/!\ Le code actuel de l'app est rĂ©alisĂ© en tant que web app, plus tard je passerais en mobile via une techno ci-dessous. Par la suite, peut-ĂȘtre voir pour une PWA ? https://github.com/angular/mobile-toolkit ; https://www.youtube.com/watch?v=vAb-2d1vcg8
- Code de prototypage, ne pas utiliser en production
- Juste un "Let's Play" donc pour le fun, c'est souvent fait dans les jeux vidéos, pourquoi pas le faire avec le dev Web
- Se lancer dans l'aventure Node.js, alors lancez-vous avec moi (je débute, donc il y aura sûrement de meilleurs pratique, n'hésitez pas d'ailleurs à les poster en commentaire tout au long du let's play)
- Explication du projet fini
(faire un Uber-Like et avec l'angouement de Pokémon GO l'été dernier, développement d'un systÚme de géocalisation in real time, ici ce sera pour connaßtre la position de nos entités, chauffeurs et passagers (driver & rider)) :
- Inscription / Connexion des clients (riders)
- DĂ©clenchement d'une course par le client
- Estimated Time of Arrival (ETA) ?
- Acceptation de la course par un chauffeur (driver)
- Récupération du rider
- DĂ©pĂŽt du rider
- Finalisation de la course (Calcule du coût en fonction du nombre de km)
- peut-ĂȘtre faire schĂ©ma ?
- Les technologies qui seront utilisées :
Ionic Framework (app hybrid), donc derriÚre ce sera du JavaScript avec AngularJS cÎté front- Angular 2 cÎté front et NativeScript ou React Native pour profiter des performances native du mobile
- Node.js cÎté back avec le micro-framework Express
MongoDB pour des data nécessitant du temps réel (temporaire)- Redis pour des data nécessitant du temps réel (temporaire ; queue à dépiler au fur et à mesure)
- Socket.io ou SSE (Server-Sent Events, cf http://stackoverflow.com/a/5326159/1768162) pour la MĂ J de la position du rider par exemple et la boucle globale d'une commande en cours (le tout saved dans Redis le temps de la course)
- MySQL pour la persistence à la fin de la commande pour préserver les données
/!\ Yarn sera utilisé à la place d'npm durant la série. Son systÚme de caching n'est pas négligeable, ainsi que son systÚme de version matching utilisé par défaut (yarn.lock)
-
Installer Node.js en allant sur : https://nodejs.org/
-
Initialisation (nom du projet : u-like)
$ npm init
-
Installation de nodemon (globalement car pas besoin de préciser un chemin dans package.json pour le start)
$ npm install -g nodemon
-
Installation de babel-cli (--save-dev transpiler du code c'est purement développement) On utilise Babel ici, car actuellement (08 novembre 2016) le moteur V8 de Google (utilisé par Node.js) ne comprend pas les import de modules ES6.
$ npm install --save-dev babel-cli
-
Installation du preset ES6
$ npm install --save-dev babel-preset-es2015
-
ESLint pour suivre des normes de développement JavaScript (ici ce sera le style guide d'Airbnb)
-
Aujourd'hui il y a un conflict entre les différentes dépendances : eslint/eslint#7338. Solution
$ npm install eslint-config-airbnb --save-dev $ npm info eslint-config-airbnb peerDependencies --json $ npm install --save-dev eslint@^3.9.1 eslint-plugin-jsx-a11y@^2.2.3 eslint-plugin-import@^2.1.0 eslint-plugin-react@^6.6.0 $ ./node_modules/.bin/eslint --init
- "Use a popular style guide"
- "Airbnb"
- "JSON"
-
Désactiver certaines rÚgles par défaut d'ESLint via .eslintrc et ajouter env node et mocha (mocha on verra par la suite mais en gros ce sera l'outil nous permettant de faire nos tests)
-
Ajouter "lint": "node_modules/.bin/eslint src/**/*.js" Ă package.json pour check toutes les sources et modifier "build"
-
IDE Settings > rechercher ESLint > Activer ESLint + renseigner package dans node_modules + ajouter config ESLint de notre projet et non de node_modules/
-
Installation de shx pour clean "dist/" avant de build et pour d'autres commandes Ă venir
$ npm install --save-dev shx
-
Ajouter start dans scripts pour le dĂ©veloppement ; Ajouter serve pour la production ; Le package.json devrait ĂȘtre similaire Ă
{ "name": "u-like", "version": "1.0.0", "description": "Just a let's play!", "main": "./src/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon --use_strict ./src/index.js --exec babel-node", "lint": "./node_modules/.bin/eslint ./src/**/*.js", "delete-dist": "shx rm -rf ./dist", "build": "npm run lint && npm run delete-dist && babel ./src -d ./dist", "serve": "node ./dist/index.js" }, "author": "Louistiti", "license": "MIT", "dependencies": { "express": "^4.14.0" }, "devDependencies": { "babel-cli": "^6.18.0", "babel-preset-es2015": "^6.18.0", "eslint": "^3.9.1", "eslint-config-airbnb": "^13.0.0", "eslint-plugin-import": "^2.1.0", "eslint-plugin-jsx-a11y": "^2.2.3", "eslint-plugin-react": "^6.6.0" } }
-
Fichier de configuration Babel ".babelrc", on indique que l'on utilisera le preset es2015
{ "presets": ["es2015"] }
Car pas de nodemon, ni de Babel en production, donc il suffira de faire
$ npm run build
$ npm run serve
De cette façon on transpile notre code ES6 en ES5, et on lance le serveur avec le code transpilĂ© de la mĂȘme maniĂšre que sur le serveur de production.
On aura besoin d'autres dépendances, mais pour le moment ça ira, on installera les autres au fur et à mesure que le projet avance.
- IDE Settings > languages & framework > node.js & npm > enable core module
- IDE Settings > languages & framework > JavaScript > ECMAScript 6
- mark :
- api/node_modules
- api/dist
- app/node_modules
- app/platforms
- app/plugins
- as exclude directory et changer en "Project Files" dans l'arbre (de cette façon lorsque l'on effectue une recherche, ce sera plus simple de retrouver les fichiers que l'on veut)
Ci-dessous la structure des dossiers / fichiers constituant le serveur. Le code transpilé (à passer en production) est dans le répertoire dist/.
- api/
- node_modules/
- sql/
- dist/
- ...
- src/
- config/
- config.js
- database.js
- server.js
- ...
- feature-name/
- ...
- helpers/
- ...
- rides/
- ride.controller.js
- ride.model.js
- ride.routes.js
- ride.spec.js
- users/
- rider.controller.js
- rider.model.js
- rider.routes.js
- rider.spec.js
- driver.controller.js
- driver.model.js
- driver.routes.js
- driver.spec.js
- validators/
- ...
- index.js
- ...
- config/
- package.json
- index.js point d'entrée (chargement des confs, appel du serveur)
- server.js (initialisation et conf du serveur)
On va utiliser Angular CLI qui va nous permettre de générer divers ressources pour notre projet tout en respectant le style guide que l'équipe d'Angular recommande (Par la suite on utilisera : https://www.npmjs.com/package/react-native-cli ou : https://github.com/NathanWalker/nativescript-ng2-magic)
Bien expliquer ce que fais un "generate", etc. d'Angular-CLI pour ne pas perdre les viewers
-
Installer Angular CLI :
$ npm install -g angular-cli
-
Modifier "spec" object dans angular-cli.json en passant tout à "false", car nous ne voulons pas faire de tests cÎté app (pas bien)
-
Créer un nouveau projet Angular :
$ ng new u-like --style=sass
(rĂ©indenter) (modifier le sĂ©lecteur du composant root par "uberlike" et ajouter dans les custom tags de l'IDE, de mĂȘme pour les composants futures)
-
Modifier attribue préfix par "uberlike" dans "tslint.json" (+ "angular-cli.json" (si utilisé))
-
Renommer dossier "u-like" par "app"
-
Supprimer app/README.md
-
Activer TSLint dans l'IDE Settings > TSLint > Enable + renseigner dossier tslint dans node_modules
-
Gérer les environnements en créant les fichiers dans environments/ et en modifiant angular-cli.json / main.ts
-
Créer "core/config.ts" pour les constantes utiles à notre projet et gérer les environnements
-
Editer app.component.html avec le nécessaire pour commencer
-
Créer nouveau composant "home" :
$ ng g c home
(réindenter) (delete home.component.spec.ts)
-
Importer le router d'Angular dans app.module.ts
-
Importer le style des composants "globaux" / shared SCSS (que j'ai déjà dev' en amont)
-
Importer "assets/scss/_includes/base/all" et styliser le "body" dans styles.scss
A REDEFINIR APRES AVOIR CHOISI ENTRE NATIVESCRIPT ET REACT NATIVE (mais toujours en respectant une structure recommandée par la team Angular)
- app/
- dist/
- node_modules/
- src/
- app/
- core/ (seulement singletons services ou items qui ont besoin d'ĂȘtre instanciĂ©s qu'une fois)
- config.ts
- http.service.ts
- ...
- home/
- home.component.html
- home.component.scss
- home.component.ts
- public/
- register/
- register.component.html
- register.component.ts
- public.module.ts
- public-routing.ts
- ...
- register/
- shared/
- loader.component.ts
- response-message.component.scss
- response-message.component.ts
- ...
- users/
- ? rider-list/ (seulement si besoin)
- rider-list.component.html
- rider-list.component.scss
- rider-list.component.ts
- rider/
- rider.component.html
- rider.component.scss
- rider.component.ts
- rider-add/
- rider-add.component.html
- rider-add.component.ts
- ? shared/ (si composants, directives, pipes propres à cette feature sont partagés ailleurs) ...
- rider.model.ts
- rider.service.ts
- riders-routing.module.ts
- riders.module.ts
- ? rider-list/ (seulement si besoin)
- app-routing.module.ts
- app.component.html
- app.component.scss
- app.component.ts
- app.module.ts
- index.ts
- not-found.component.ts
- ...
- core/ (seulement singletons services ou items qui ont besoin d'ĂȘtre instanciĂ©s qu'une fois)
- assets/
- images/
- scss/
- index.html
- main.ts
- polyfills.ts
- styles.scss
- ...
- app/
- ...
-
Installer Express (--save car c'est une dépendance pour faire tourner notre application)
$ npm install express --save
-
Setup configs + middlewares (server class, ...) Utilisation de import ES6 au lieu des requires, pour sélectionner la partie des modules qui nous intéresse. Plus performant, on a une mémoire plus libre.
-
First middleware
// Disable from the header, else it makes hacker's life easier to know more about our system res.removeHeader('X-Powered-By'); console.log('request', `${req.method} ${req.url}`);
-
Vérifier que le démon (serveur) MySQL est lancé (Windows : services ; mysqld). Sinon le lancer (possible répertoire Wamp, etc.)
-
Se connecter au serveur MySQL (vos identifiants, ici pas de password) :
$ mysql -h localhost -u root
-
Créer la BDD (utf8mb4_unicode_ci)
> CREATE DATABASE uberlike COLLATE utf8mb4_unicode_ci; > exit
- Connexion via PhpStorm (ou autre database manager)
- Création de la table "rider" (passagers) https://i.gyazo.com/b484423d7fa08914cb473631b6f620d7.png
-
Installer MySQL dans le projet
$ npm install mysql --save
-
Configurer la connexion Ă MySQL A savoir que nous nous connectons qu'une fois Ă la base de donnĂ©es, au lancement du serveur. Ensuite le serveur attend de nouvelle requĂȘtes (http://i.imgur.com/Hqv5LlG.gifv :D )
Créer config/database.js
-
Ajouter middleware dans bootstrap() de config/server.js
-
Création de l'entité "rider"
- users/
- rider.controller.js
- rider.model.js
- rider.routes.js
- users/
-
Travailler les paramĂštres sur des requĂȘtes ayant un verb autre que GET
$ npm install body-parser --save
// Parse input values in JSON format app.use(bodyParser.json()); // Parse from x-www-form-urlencoded, which is the universal content type app.use(bodyParser.urlencoded({ extended: true }));
-
On installe un package pour la validation de nos données
$ npm install validator --save
-
On installe un package pour générer des uuids (pour identifier nos entités)
$ npm install uuid --save
-
On va chiffrer le mot de passe
$ npm install bcrypt --save
Ajouter dossier helpers avec premier helper pour les problĂ©matiques de temps (ici datetime()). Faire logique d'ajout en base de donnĂ©es + errors handling (avec EventEmitter) + tester requĂȘte avec Postman.
Créer structure des retours endpoints (succÚs et erreur) via helper "response.js"
Nous allons bientÎt attaquer nos premiers tests. Pour ce faire nous allons d'abord créer nos différents environnements afin d'agir en conséquence. Ici nous aurons : test ; dev ; prod. L'env' de dev étant celui par défaut.
-
Séparer les configs pour la connexion à la base de données et initialiser "process.env.NODE_ENV" : api/config/config.js
-
Remplacer appel de "db" qui est maintenant une fonction
-
Créer dossier api/sql et ajouter le script de reset de la BDD test "reset-test-db.sql"
-
Modifier les scripts du package.json pour créer une BDD dédiée aux tests en clonant la structure de la BDD dev à la volée
"scripts": { "clone-db-test": "mysql -h localhost -u root < ./sql/reset-test-db.sql && mysqldump --no-data uberlike -h localhost -u root > ./sql/uberlike.sql && shx sed -i AUTO_INCREMENT=[0-9] AUTO_INCREMENT=0 ./sql/uberlike.sql && mysql uberlike_test -h localhost -u root < ./sql/uberlike.sql", "test": "set NODE_ENV=test&& npm run clone-db-test && node ./dist/index.js", "start": "nodemon --use_strict ./src/index.js --exec babel-node", "lint": "./node_modules/.bin/eslint ./src/**/*.js", "build": "npm run lint && babel ./src -d ./dist && npm test", "serve": "set NODE_ENV=prod&& node ./dist/index.js" }
On vient de prendre conscience de nos différents environnements et de créer notre premier endpoint,maintenant automatisons son test. En effet ces tests vont nous assurer que notre projet est périn dans le temps. Imaginons que demain nous ajoutons une feature Y qui impact une feature X, il est pas impossible que cette feature X ne fonctionne plus (effet de bord) et que nous le remarquons pas. Les tests vont nous permettre de répondre à cette problématique.
Ici nous allons seulement faire des tests sur l'API, des tests d'intĂ©grations qui regroupent nos petites briques (qui elles devraient ĂȘtre testĂ©es via des tests unitaires), donc tester nos endpoints. Si l'on fait tests unitaires + tests d'intĂ©grations + tests de validations ce serait trop long Ă tout dĂ©montrer. Libre Ă vous de les ajouter. ;)
-
Installer Mocha : framework pour nos tests (--save-dev car dépendance qu'on a besoin seulement en phase de dev)
$ npm install mocha --save-dev
-
Installer Chai (pour les assertions)
$ npm install chai --save-dev
-
Installer Chai HTTP (exĂ©cuter des requĂȘtes pour tester notre API et coupler nos assertions avec)
$ npm install chai-http --save-dev
-
Installer Chai Things (ajoute du support aux assertions sur les tableaux. Utile pour nous car nous avons un tableau d'erreurs)
$ npm install chai-things --save-dev
-
Modifier rĂšgle "import/no-extraneous-dependencies" dans .eslintrc seulement pour les tests
-
Modifier le script de test dans "package.json" en exécutant Mocha, en précisant que l'on est sur de l'ES6 et parce qu'on aime les chats alors avoir le reporter Nyan Cat (on exécute init et riders en priorité)
"test": "set NODE_ENV=test&& npm run clone-db-test && mocha --compilers js:babel-register --reporter nyan ./src/init.spec.js ./src/users/rider.spec.js ./src/**/*.spec.js",
-
Nous allons donc découper nos tests par feature, ici on va commencer par "init.spec.js" et "rider.spec.js" (seulement créer les fichiers)
Utilisation d'expect() au lieu de should(), son import ES6 est plus propre Ă mon sens car should() doit patch les objets avant de pouvoir ĂȘtre utilisĂ©. AprĂšs ce sont les goĂ»ts et les couleurs.
-
Ajout du helper "log" pour avoir de jolies couleurs dans notre console
-
Remplacer tous les console.log()
-
Remplir "init.spec.js"
-
Remplir "rider.spec.js" pour POST /v1/riders
Here we go
- Dans les dossiers shared/ devront figurer uniquement les composants, pipes et directives (voir aussi services si propre à la feature courante, mais à éviter) qui sont utilisés ailleurs que dans la feature courante (si relatif à aucune feature, alors mettre dans dossier shared/ à la racine)
- Faire feature/shared/ quand un modÚle et / ou un service par exemple sont utilisés ailleurs que dans la feature concernée
- Dans core/ devront figurer uniquement les singletons services ou items qui ont besoin d'ĂȘtre instanciĂ©s qu'une fois
core.module.ts
doit ĂȘtre importĂ© seulement dansapp.module.ts
- Faire un garde dans
core.module.ts
et tous les modules Ă©tant dans core/ pour ĂȘtre sĂ»r que ceux-ci ne soient pas importĂ©s dans d'autres modules (cf https://angular.io/styleguide#!#04-12)
-
Faire le squelette de l'application, avec un routing enfant (riders), composant "public/register", "users/rider-add", "riders" dans dossier "users" Car on aura un module routing spécifique et un module de chargement à chaque feature / composant "métier" de notre application. Préparer module
core/core.module.ts
etshared/shared.module.ts
. -
Editer le composant (rendu + style) Home et Register, tout ce dont on a besoin pour inscrire un utilisateur (voir pour faire rider + driver, pas sûr)
-
Logique métier (validations de form, avec patterns, maxlength, minlength) + créer model "users/rider.model.ts" (les modÚles vont nous servir pour les data provenant d'un service, cf https://angular.io/docs/ts/latest/guide/reactive-forms.html#!#_reactive_-forms)
- styliser les validations.
Maintenant que le formulaire est prĂȘt, il ne nous manque plus qu'Ă envoyer les donnĂ©es Ă notre API. Pour ce faire on utilisera le client HTTP d'Angular.
-
Créer un client HTTP custom qui va surcharger celui fournis par Angular, de cette façon on n'aura pas à répéter notre code pour le catch d'erreur, authentification, etc. (étant abstrait, le type de "back-end" est un XHRBackend et non ConnectionBackend pour le constructeur parent de notre client HTTP custom)
-
Structurer comme il faut le client HTTP dans le projet en utilisant un service dédié à chaque "feature" / modÚle
-
Faire requĂȘte d'inscription
-
Back : créer nouvel objet literal "app" dans la config api
-
Back : configurer le CORS dans un middleware en fonction de l'environnement actuel
-
Afficher messages retournés par le serveur en créant le composant "ResponseMessageComponent"
-
Styliser le nouveau composant
-
Faire composant "NotFound" + styliser un peu avec des GIFs random
-
Faire composant "Loader" et binder "isLoading" quand nécessaire, ici avec le composant parent "RegisterRider"
Loader : http://image.noelshack.com/fichiers/2016/51/1482167158-button-loader.gif
Commit : https://github.com/Louistiti/Uber-Like/tree/7f54794826c90ef9887ba8b090e8cdf9d3c5375a
(peut-etre à traiter plus tard pour les vidéos, car restructuration faite) (si problÚme de chargement de module durant cette partie, alors refaire un "ng serve" car Webpack peut avoir des conflits pour charger des modules Angular "on the fly")
-
A l'avenir l'application va devenir plus importante, Angular-CLI utilise Webpack pour séparer notre code en "bundle", lors du premier chargement de page nous avons pour le moment 3.6MB de chargé (non minifié) : https://i.gyazo.com/f9ed604aad00451f50c9dca3f0941b88.png
-
On va séparer notre composant "Register" de notre application, car actuellement il est chargé via "app.module". Dans "app.module" on ne charge seulement ce dont on a besoin pour nos composants "racine". On va créer un module de chargement + un module routing pour chaque feature métiers de notre application. De cette façon, notre code sera bien séparé et via le lazy loading on piochera seulement le nécessaire au moment du changement de route. Ce qui donnera un premier chargement de l'application (sans cache) plus performant.
-
DĂ©roulement : app.module > app-routing.module (indique quelles routes doient ĂȘtre lazy loaded) > feature.module > feature-routing.module
-
Pour info', la team Angular a vraiment bien pensée les choses car nous pouvons également faire du pre-loading, cf https://angular.io/docs/ts/latest/guide/router.html#!#preloading. En fonction de la stratégie choisie, il est possible de preloaded les modules lorsque les modules nécessaires pour la feature ou route en cours ont correctement été chargés. Mais on verra ça plus tard.
Maintenant voici un screenshot avec le lazy loading lors du premier chargement de page : https://i.gyazo.com/d1c1f22606ec1a6a8867d98ece16c436.png. Remarquons la différence de taille des données transferées, ici on est à 3.4MB au lieu de 3.6MB.
Lorsque l'on appel notre route "register" : https://i.gyazo.com/d426d391b73b32461d6570000fecf9a5.png on peut voir qu'un chunk est chargé. Ce chunk correspond à notre "RegisterModule" qui lui va s'occuper de charger les dépendances nécessaires à ses composants.
Commit : https://github.com/Louistiti/Uber-Like/tree/d1be1a36634bab1a3cbf26faf13e2e669646e17d
On retourne maintenant cÎté back.
-
On utilisera un JWT (Json Web Token) pour authentifier nos utilisateurs.
-
Grùce à une clé secrÚte (donc seulement connue par le serveur), on va pouvoir signer ce token, voyez-y comme un certificat.
-
Les JWT protĂšgent directement contre les failles CSRF Ă©tant stateless (pas de sessions, mais un token).
-
Permet de ne pas stocker les identifiants en local (donc pas de mot de passe, etc.), et le token a une durée de vie (ici 1 heure).
-
Si désactivation de compte : clear tokens de l'app + révoquer tous les devices de l'utilisateur
-
Si déconnexion : clear tokens de l'app + révoquer device courant de l'utilisateur
-
Si changement de mot de passe : révoquer tous les devices de l'utilisateur excepté le device courant
-
Imaginez un refresh token comme Ă©tant l'option "se souvenir de moi" dans une SPA.
-
Un refresh token rĂ©duira le champs d'action sur la durĂ©e pour un attaquant. En effet l'access_token est valide 1h, le refresh_token peut ĂȘtre valide bcp plus longtemps, mĂȘme "Ă vie". J'ai tout de mĂȘme prĂ©fĂ©rĂ© lui donner une durĂ©e de vie de 7 jours.
-
On pourra révoquer un refresh token, donc l'accÚs à un device spécifique.
-
Notre systĂšme d'authentification est multi-devices, il est possible d'ĂȘtre connectĂ© sur un mĂȘme compte via plusieurs clients car chaque device a son propre refresh_token
-
Expliquer ce qu'est un JWT (composé de 3 parties, https://jwt.io, etc.) Par conséquent on pourra créer des "gardes" (sous forme de middleware) pour dire "t'es un rider, donc tu peux ou ne peux pas accÚder à cette ressource" sans tapper dans la BDD On pourra aussi connaßtre l'utilisateur qui demande l'accÚs à la ressource (via l'uuid) Et par convention, quel device (via client_id / deviceId) via le claim "sub" pour "Subject" Générer access_token et refresh_token, donc créer table "device"
-
Expliquer comment fonctionne l'authentification pour notre projet. On va utiliser le mĂȘme process que le protocol OAuth 2.0 (cf "Figure 2" : https://tools.ietf.org/html/rfc6749#section-1.5) On veut que notre projet soit multi-devices, donc possibilitĂ© d'ĂȘtre authentifiĂ© sur plusieurs appareils en mĂȘme temps. Par consĂ©quent on par du principe qu'un utilisateur peut avoir plusieurs devices, qui eux vont ĂȘtre authentifiĂ©
- RequĂȘte : /auth/token (email=xxx&password=xxx&user_typer=rider|driver&grant_type=password)
- RĂ©ponse : https://i.gyazo.com/2f697fb402116b23c9a8f128982ba6c4.png
- RequĂȘte : /ressource-protĂ©gĂ©e (Authorization: Bearer access_token)
- Réponse : infos de la ressource protégée
- Reproduire étape 3 et 4 jusqu'à ce que access_token expire (ou anticiper l'expiration avec expires_in retournée dans l'étape 2, à l'heure actuelle je ne sais pas encore ce que je vais faire ici)
- Dans le cas oĂč il n'y a pas d'anticipation, et que access_token a expirĂ©. RĂ©ponse : https://i.gyazo.com/c18dc14c932b271a7f1c9eebd6a04f13.png
- RequĂȘte : /auth/token (refresh_token=xxx&grant_type=refresh_token&client_id=xxx)
- RĂ©ponse : pareil que l'Ă©tape 2 avec un nouveau access_token et refresh_token
- Il est primordial d'utiliser HTTPS pour les Ă©changes client / serveurs.
- Sauvegarder access_token et refresh_token dans un cookie "Secure", "HttpOnly" et "path".
- Secure : HTTPS
- HttpOnly : Contre les XSS par exemple (pas d'accĂšs au cookie via un script par exemple),
- accĂšs au cookie seulement via le protocol HTTP.
- Path: "/auth/token" pour restreindre le cookie Ă ce path
-
Package express-jwt (middleware pour décoder les JWT)
$ npm install express-jwt --save
-
Package jsonwebtoken (générer les JWT)
$ npm install jsonwebtoken --save
-
Ajouter objet "access_token" et attribues "secret", "exp" dans config.js
-
Créer "timestamp()" (time.js) helper pour la validité du JWT dans le temps et "string" helper
-
Faire middleware JWT dans config/server.js, celui qui va s'occuper de décoder et de dire si l'access_token est valide ou non
-
Faire middleware pour traiter les erreurs erreurs liés au JWT (middlewares/authError.js) https://i.gyazo.com/c18dc14c932b271a7f1c9eebd6a04f13.png
-
Faire rider guard middleware et l'associer aux ressources /riders concernées (middlewares/riderGuard.js) https://i.gyazo.com/85ad07c0a7b5939776415822e118dade.png (tenter d'accÚder à une ressource rider quand on est driver par exemple)
Voilà nous avons posté nos gardiens devant notre chùteau, maintenant on va voir comment créer notre JWT via le package "jsonwebtoken" que l'on a installé.
-
Faire route racine /auth dans "server.js" + créer dossier "auth" comprenant "auth.routes.js" et faire la route "/auth/token" (possibiltié de décenralisé le tout sur un serveur différent)
-
Faire "auth/auth.controller.js" avec action "create"
-
Ecrire code pour le grant_type=password jusqu'à avoir le retour https://i.gyazo.com/2f697fb402116b23c9a8f128982ba6c4.png (besoin de créer devices/device.model.js)
- Faire process avec grant_type=refresh_token Donc créer nouvelle table "device" qui contiendra nos clients, donc les différents appareils que pourraient utiliser l'utilisateur https://i.gyazo.com/9292aab348d8b5056e2ebdc7a1a5ac68.png Faire nouveau helper "validator" pour checker si le refresh_token est bien un SHA-1, car le package "Validator" ne gÚre pas se cas
Comme on l'a dit, l'utilisateur peut révoquer l'accÚs d'un appareil spécifique à son compte. Il peut aussi renommer le nom de cet appareil pour que ce soit plus "user friendly". /devices/:uuid (refresh_token|name)
-
Créer routes nécessaires /devices
-
Faire devices/device.controller.js + créer action "edit"
-
Retour révocation d'un appareil : https://i.gyazo.com/a2e9c539ce33a987a9df242f1bfe349e.png
-
Retour Ă©dition du nom d'un appareil : https://i.gyazo.com/67a67005eae2ce82e0eb0dc8c63f034c.png
- Faire les specs couvrant l'authentification / autorisation / révocation (auth.spec.js + device.spec.js)
(peut-etre à traiter plus tard pour les vidéos, car restruction faite) Comme dit dans un épisode précédent : "En fonction de la stratégie choisie, il est possible de preloaded les modules lorsque les modules nécessaires pour la feature ou route en cours ont correctement été chargés. Mais on verra ça plus tard."
Avec la stratĂ©gie "PreloadAllModules", tous les modules qui tendent Ă ĂȘtre lazy loaded seront chargĂ©s. Pour ça il suffit simplement d'importer le module "PreloadAllModules" Ă notre routing principal et d'y spĂ©cifier la stratĂ©gie.
Il est également possible d'utiliser une stratégie personnalisée qui nous laissera le choix sur les modules que l'on veut preloaded, sans dépendre du lazy loading, c'est la stratégie que l'on va utiliser pour une meilleure souplesse.
-
Créer "core/selective-preloading-strategy", cf https://angular.io/docs/ts/latest/guide/router.html#custom-preloading-strategy
-
Ajouter la stratégie dans "app-routing.module.ts"
-
Ajouter "preload: true" Ă la route "register"
Maintenant, en plus d'ĂȘtre lazy loaded, nos routes peuvent ĂȘtre preloaded. Attention tout de mĂȘme de ne pas en abuser, ici on sait que si l'utilisateur n'est pas authentifiĂ©, il a de grande chance d'attĂ©rir sur l'inscription, c'est pour ça que l'on peut se permettre de preloaded.
Voici le déroulement général de l'authentification :
-
Rediriger le rider sur la vue de connexion aprĂšs inscription
-
Se connecter
-
RĂ©cupĂ©rer le JWT + refresh_token + client_id et stocker les trois entitĂ©s dans trois cookies diffĂ©rents en rĂ©pondant bien aux spĂ©cificitĂ©s du dessus Ă ce sujet (au choix : possibilitĂ© de rĂ©cupĂ©rer "expires_in", set date d'expiration et enregistrer le tout en local storage pour anticiper Ă chaque requĂȘte / changement de vue la MĂ J du JWT (access_token). L'idĂ©ale serait de faire ça avec les sockets) Ici il faudra crĂ©er
core/utils.ts
. -
Envoyer le JWT Ă chaque requĂȘte, si JWT non trouvĂ© alors vĂ©rifier si refresh_token et client_id existent sont prĂ©sent, si oui, demander nouveau JWT, si un des deux derniers manquent, alors rediriger sur vue de connexion
-
Si 401 retourné, alors vérifier si refresh_token et client_id existent sont présent, si oui, demander nouveau JWT, si un des deux derniers manquent, alors rediriger sur vue de connexion
-
A chaque changement de vue, vérifier si JWT présent (guards), si non rediriger sur vue de connexion
-
Créer composant de connexion dans
public/sign-in/
-
Créer service
auth.service.ts
-
Installer angular2-jwt
-
Créer guard
auth-guard.service.ts
-
Créer guard
unauth-guard.service.ts
-
Créer
protected/
module -
GĂ©rer les guards via
app-routing.module.ts
-
Faire conditions dans le header et sur la home
-
Récupérer données "basiques" de l'utilisateur
-
Faire header
-
Faire map
OLD :
Une fois connectĂ© l'utilisateur sera redirigĂ© vers le "dashboard", l'accĂšs Ă cette feature doit ĂȘtre protĂ©gĂ©e via un garde
(de la mĂȘme façon que pour le backend), ici on utilisera "CanActivate" qui sera en fait un service que l'on va crĂ©er
dans core/auth-guard.service.ts, cf https://angular.io/docs/ts/latest/guide/router.html#!#guard-the-admin-feature.
Cependant, le "dashboard" sera toujours preloaded, mĂȘme si l'utilisateur n'est pas connectĂ©. Pour pallier à ça
il faut ajouter un nouveau garde : "CanLoad" qui utilisera la mĂȘme logique que "CanActivate" mais pour vĂ©rifier
si il faut charger ou non le module adéquat, cf https://angular.io/docs/ts/latest/guide/router.html#!#can-load-guard.
Les gardes "CanActivate" et "CanLoad" seront ajoutés devant les portes de chaque feature nécessitant l'authentification.
[En cours]
Différencier les riders des drivers à l'inscription et à l'authentification.
FIXER PROBLEMATIQUE : "La boucle qui controle le temps dâannulation tourne bien sur le serveur et nâattend pas la mise Ă jour de la webapp ? parceque la il ne sâest rien passĂ© pendant 20 min, jusquâa ce que le Majordome relance son navigateur" Rendre le serveur autonomme. Node.js corrige dĂ©jà ça ? Passer par les websockets (socket.io) ?
UTILISER LES WEBSOCKETS AVEC SOCKET.IO POUR ACTUALISER LA POSITION DU DRIVER cf http://stackoverflow.com/questions/31715179/differences-between-websockets-and-long-polling-for-turn-based-game-server
FAIRE BARRE DE PROGRESSION ANIME (PLUS LE DRIVER APPROCHE, PLUS LA COULEUR DEVIENT FONCE)
-
Utiliser "export default" lorsqu'il n'y a seulement qu'un export dans le fichier
-
Ne pas exporter des entités mutables (var, let)
-
Préciser que l'on utilisera SCSS si projet déjà créé avec Angular-CLI
$ ng set defaults.styleExt scss
- Ceci ajoute une rÚgle à la config Angular-CLI dans le fichier angular-cli.json. Dans angular-cli.json préciser styles.scss et le créer)
-
Compiler pour la prod' avec Angular-CLI
$ ng build --prod
-
Package pour comparer et mettre à jour les dépendances d'un projet
$ npm install -g npm-check-updates
- Comparer les version actuelles (fait un "npm outdated" en gros)
$ ncu
- Upgrade les versions actuelles
$ ncu -u
- Comparer les version actuelles (fait un "npm outdated" en gros)
-
ProblÚme, en production lorsque l'on tente d'accÚder à une route qui n'est pas la racine, on tombe sur une 404 car le Web server ne connaßt que la racine. Il faut donc préciser que si une ressource n'existe pas, alors rediriger sur index.html qui s'occupera de charger le nécessaire. Générer fichier .htaccess :
RewriteEngine On # If an existing asset or directory is requested go to it as it is RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d RewriteRule ^ - [L] # If the requested resource doesn't exist, use index.html RewriteRule ^ /index.html
-
Ajouter une dépendance
$ yarn add [package]
-
Ajouter dépendance de développement
$ yarn add [package] [--dev/-D]
-
Mettre à jour les dépendances
$ yarn upgrade
- https://code.tutsplus.com/tutorials/build-a-complete-mvc-website-with-expressjs--net-34168
- https://blog.risingstack.com/node-hero-node-js-project-structure-tutorial/
- https://angular.io/styleguide#!#app-structure-and-angular-modules
- https://www.linkedin.com/pulse/brief-introduction-mongodb-mysql-mohammadreza-faramarzi
- http://www.theserverside.com/feature/How-NoSQL-MySQL-and-MogoDB-worked-together-to-solve-a-big-data-problem
- https://www.quora.com/Why-does-Quora-use-MySQL-as-the-data-store-instead-of-NoSQLs-such-as-Cassandra-MongoDB-or-CouchDB
- http://gotocon.com/dl/goto-aar-2014/slides/MartyWeiner_ScalingPinterest.pdf
- http://stackoverflow.com/questions/7888880/what-is-redis-and-what-do-i-use-it-for
- https://tools.ietf.org/html/rfc6749
- https://tools.ietf.org/html/rfc7519
- https://blog.hyphe.me/using-refresh-tokens-for-permanent-user-sessions-in-node/
- https://auth0.com/forum/t/can-i-change-a-jwts-expiration-field-in-order-to-invalidate-it/1198
- http://security.stackexchange.com/questions/91116/is-my-jwt-refresh-plan-secure
- https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
- http://stackoverflow.com/questions/26739167/jwt-json-web-token-automatic-prolongation-of-expiration/26834685#26834685
- https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage#so-whats-the-difference
https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#!#q-module-recommendations http://stackoverflow.com/questions/42779871/angular-core-feature-shared-modules-what-goes-where
https://angular.io/docs/ts/latest/guide/router.html#the-heroes-app-code https://angular.io/docs/ts/latest/guide/router.html#add-heroes-functionality https://scotch.io/tutorials/routing-angular-2-single-page-apps-with-the-component-router
http://jasonwatmore.com/post/2016/09/29/angular-2-user-registration-and-login-example-tutorial http://angularjs.blogspot.fr/2016/11/easy-angular-authentication-with-json.html
- http://node.green/
- http://stackoverflow.com/questions/22891211/what-is-difference-between-save-and-save-dev
- bcrypt https://codahale.com/how-to-safely-store-a-password/
- Utiliser chai Should dans ES6 http://chaijs.com/guide/styles/#using-should-in-es2015
- ngCordova : module Cordova pour Angular pour profiter des composants natifs http://ngcordova.com/docs/install/ (bower install ngCordova)
- http://ngcordova.com/docs/plugins/geolocation/ (cordova plugin add cordova-plugin-geolocation)
- Utiliser Sass avec Ionic : http://ionicframework.com/docs/cli/sass.html (ionic setup sass)
- Package plumber (npm install --save-dev gulp-plumber) de cette façon ça stoppera pas la tùche, mais affichera les erreurs liées à notre style
- Express : http://expressjs.com/fr/ (micro-framework)
- Nodemon : https://github.com/remy/nodemon (recharge automatiquement application node lorsqu'un fichier est modifié)
- Babel : https://babeljs.io/ transformer ES6 (ECMAScript 2015) en ES5. Implémentation Node.js : https://github.com/babel/example-node-server
- ESLint : https://github.com/eslint/eslint
- Package eslint-config-airbnb : https://www.npmjs.com/package/eslint-config-airbnb
- Package eslint-plugin-import : https://www.npmjs.com/package/eslint-plugin-import
- eslint-plugin-jsx-a11y : https://www.npmjs.com/package/eslint-plugin-jsx-a11y
- eslint-plugin-react : https://www.npmjs.com/package/eslint-plugin-react
- Package mysql : https://www.npmjs.com/package/mysql
- Package body-parser : https://www.npmjs.com/package/body-parser
- Package validator : https://www.npmjs.com/package/validator
- Package uuid : https://www.npmjs.com/package/uuid
- Package bcrypt : https://www.npmjs.com/package/bcrypt
- Package mocha : https://www.npmjs.com/package/mocha
- Package chai : https://www.npmjs.com/package/chai
- Package chai-http : https://www.npmjs.com/package/chai-http
- Package chai-things : https://www.npmjs.com/package/chai-things
- Package express-jwt : https://www.npmjs.com/package/express-jwt
- Package jsonwebtoken : https://www.npmjs.com/package/jsonwebtoken
- Package shx : https://www.npmjs.com/package/shx
- Package ngxerrors : https://www.npmjs.com/package/@ultimate/ngxerrors
- Package ng2-cookies : https://www.npmjs.com/package/ng2-cookies
- Package angular2-jwt : https://github.com/auth0/angular2-jwt
Louis Grenard : https://www.louistiti.fr
MIT License
Copyright (c) 2016 Louistiti [email protected]
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.