🚗 Un prototype de Uber pour le fun (serveur + application)
JavaScript TypeScript CSS HTML

README.md

Uber-Like

MIT License

Uber-Like Logo

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. :)

1- Introduction

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 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

2- Installation & Pré-requis

Back-End

Installation

  1. Installer Node.js en allant sur : https://nodejs.org/

  2. Initialisation (nom du projet : u-like)

    $ npm init
  3. Installation de nodemon (globalement car pas besoin de préciser un chemin dans package.json pour le start)

    $ npm install -g nodemon
  4. 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
  5. Installation du preset ES6

    $ npm install --save-dev babel-preset-es2015
  6. ESLint pour suivre des normes de développement JavaScript (ici ce sera le style guide d'Airbnb)

  7. Aujourd'hui il y a un conflict entre les différentes dépendances : https://github.com/eslint/eslint/issues/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"

  1. 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)

  2. Ajouter "lint": "node_modules/.bin/eslint src/*/.js" Ă  package.json pour check toutes les sources et modifier "build"

  3. IDE Settings > rechercher ESLint > Activer ESLint + renseigner package dans node_modules + ajouter config ESLint de notre projet et non de node_modules/

  4. Installation de del-cli pour clean "dist/" avant de build

    $ npm install --save-dev del-cli
  5. 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": "./node_modules/.bin/del-cli -f ./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"
      }
    }
  6. 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.

Configuration de l'IDE (PhpStorm)

  1. IDE Settings > languages & framework > node.js & npm > enable core module
  2. IDE Settings > languages & framework > JavaScript > ECMAScript 6
  3. 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)

Structure

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
      • ...
    • package.json
A savoir
  • index.js point d'entrĂ©e (chargement des confs, appel du serveur)
  • server.js (initialisation et conf du serveur)

Front-End (faire nouvelle vidéo ici)

Installation

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

  1. Installer Angular CLI :

    $ npm install -g angular-cli
  2. Modifier "spec" object dans angular-cli.json en passant tout à "false", car nous ne voulons pas faire de tests cÎté app (pas bien)

  3. 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)

  4. Modifier attribue préfix par "uberlike" dans "tslint.json" (+ "angular-cli.json" (si utilisé))

  5. Renommer dossier "u-like" par "app"

  6. Supprimer app/README.md

  7. Activer TSLint dans l'IDE Settings > TSLint > Enable + renseigner dossier tslint dans node_modules

  8. Créer "core/config.ts" pour les constantes utiles à notre projet

  9. Editer app.component.html avec le nécessaire pour commencer

  10. Créer nouveau composant "home" :

    $ ng g c home

    (réindenter) (delete home.component.spec.ts)

  11. Importer le router d'Angular dans app.module.ts

  12. Importer le style des composants "globaux" / shared SCSS (que j'ai déjà dev' en amont)

  13. Importer "assets/scss/_includes/base/all" et styliser le "body" dans styles.scss

Structure

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/
          • config.ts
          • http.service.ts
          • ...
        • home/
          • home.component.html
          • home.component.scss
          • home.component.ts
        • register/
          • register-rider/
            • register-rider.component.html
            • register-rider.component.ts
          • register-routing.module.ts
          • register.component.html
          • register.component.ts
          • register.module.ts
        • users/
          • rider-detail/
            • rider-detail.component.html
            • rider-detail.component.scss
            • rider-detail.component.ts
          • rider.model.ts
          • rider.service.ts
          • riders-routing.module.ts
          • riders.module.ts
        • app-routing.module.ts
        • app.component.html
        • app.component.scss
        • app.component.ts
        • app.module.ts
        • index.ts
        • not-found.component.ts
        • ...
      • assets/
        • images/
        • scss/
      • index.html
      • main.ts
      • polyfills.ts
      • styles.scss
      • ...
    • ...

3- Création du serveur

  1. Installer Express (--save car c'est une dépendance pour faire tourner notre application)

    $ npm install express --save
  2. 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.

  3. 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}`);

4- Création et connexion à la base de données

Création de la base de données

  1. Vérifier que le démon (serveur) MySQL est lancé (Windows : services ; mysqld). Sinon le lancer (possible répertoire Wamp, etc.)
  2. Se connecter au serveur MySQL (vos identifiants, ici pas de password) :

    $ mysql -h localhost -u root
  3. Créer la BDD (utf8mb4_unicode_ci)

    > CREATE DATABASE uberlike COLLATE utf8mb4_unicode_ci;
    > exit

Structure

  1. Connexion via PhpStorm (ou autre database manager)
  2. Création de la table "rider" (passagers) https://i.gyazo.com/c4f6f2de6431b9387ea53946c7c64e4d.png

Code

  1. Installer MySQL dans le projet

    $ npm install mysql --save
  2. 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

5- POST /riders

  1. Ajouter middleware dans bootstrap() de config/server.js
  2. Création de l'entité "rider"
    • users/
      • rider.controller.js
      • rider.model.js
      • rider.routes.js
  3. 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
    }));
  4. On installe un package pour la validation de nos données

    $ npm install validator --save
  5. On installe un package pour générer des uuids (pour identifier nos entités)

    $ npm install uuid --save
  6. 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.

6- Uniformiser nos retours JSON

Créer structure des retours endpoints (succÚs et erreur) via helper "response.js"

Erreurs

alt text

SuccĂšs

alt text

7- Environnements

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. 1. Séparer les configs pour la connexion à la base de données et initialiser "process.env.NODE_ENV" : api/config/config.js

  1. Remplacer appel de "db" qui est maintenant une fonction

  2. Créer dossier api/sql et ajouter le script de reset de la BDD test "reset-test-db.sql"

  3. 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_test.sql && mysql uberlike_test -h localhost -u root < ./sql/uberlike_test.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"
      }
    

8- Configuration de nos tests

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. ;)

  1. 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
    
  2. Installer Chai (pour les assertions)

    $ npm install chai --save-dev
  3. Installer Chai HTTP (exĂ©cuter des requĂȘtes pour tester notre API et coupler nos assertions avec)

    $ npm install chai-http --save-dev
  4. 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
  5. Modifier rĂšgle "import/no-extraneous-dependencies" dans .eslintrc seulement pour les tests

  6. 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",
  7. 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)

9- Notre premier test

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.

  1. Ajout du helper "log" pour avoir de jolies couleurs dans notre console

  2. Remplacer tous les console.log()

  3. Remplir "init.spec.js"

  4. Remplir "rider.spec.js" pour POST /v1/riders

Here we go

alt text

10- Vue d'inscription

  1. Faire le squelette de l'application, avec un routing enfant (riders), composant "register", "register-rider", "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" pour les ressources que l'on utilise souvent (loader, ...)

  2. 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)

  3. Logique métier (validations de form, avec patterns, maxlength, minlength) + créer model "users/rider.model.ts" et binder les données formulaire avec [(ngModel)] Utiliser variable locale (#foo) pour faire les validations + styliser les validations.

11- Envoyer la requĂȘte d'inscription

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.

  1. 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)

  2. Structurer comme il faut le client HTTP dans le projet en utilisant un service dédié à chaque "feature" / modÚle

  3. Faire requĂȘte d'inscription

  4. Back : créer nouvel objet literal "app" dans la config api

  5. Back : configurer le CORS dans un middleware en fonction de l'environnement actuel

  6. Afficher messages retournés par le serveur en créant le composant "ResponseMessageComponent"

  7. Styliser le nouveau composant

12- Composant Loader + page 404

  1. Faire composant "NotFound" + styliser un peu avec des GIFs random

  2. 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

Home + Register Rider

13- Lazy loading

Commit : https://github.com/Louistiti/Uber-Like/tree/7f54794826c90ef9887ba8b090e8cdf9d3c5375a

(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")

  1. 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

  2. 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.

  3. DĂ©roulement : app.module > app-routing.module (indique quelles routes doient ĂȘtre lazy loaded) > feature.module > feature-routing.module

  4. 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.

14- Préparation à l'authentification

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

  1. 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"

  2. 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Ă©

    1. RequĂȘte : /auth/token (email=xxx&password=xxx&user_typer=rider|driver&grant_type=password)
    2. RĂ©ponse : https://i.gyazo.com/2f697fb402116b23c9a8f128982ba6c4.png
    3. RequĂȘte : /ressource-protĂ©gĂ©e (Authorization: Bearer access_token)
    4. Réponse : infos de la ressource protégée
    5. 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)
    6. 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
    7. RequĂȘte : /auth/token (refresh_token=xxx&grant_type=refresh_token&client_id=xxx)
    8. 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

  1. Package express-jwt (middleware pour décoder les JWT)

    $ npm install express-jwt --save
    
  2. Package jsonwebtoken (générer les JWT)

    $ npm install jsonwebtoken --save
    
  3. Ajouter objet "access_token" et attribues "secret", "exp" dans config.js

  4. Créer "timestamp()" (time.js) helper pour la validité du JWT dans le temps et "string" helper

  5. 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

  6. Faire middleware pour traiter les erreurs erreurs liés au JWT (middlewares/authError.js) https://i.gyazo.com/c18dc14c932b271a7f1c9eebd6a04f13.png

  7. 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é.

15- Authentification

  1. 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)

  2. Faire "auth/auth.controller.js" avec action "create"

  3. 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)

16- Mise Ă  jour d'un token d'authentification

  • 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/619f1fe6058e924de1a40cd4dea051e0.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

17- Renommer et révoquer un appareil

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)

  1. Créer routes nécessaires /devices

  2. Faire devices/device.controller.js + créer action "edit"

  3. Retour révocation d'un appareil : https://i.gyazo.com/a2e9c539ce33a987a9df242f1bfe349e.png

  4. Retour Ă©dition du nom d'un appareil : https://i.gyazo.com/67a67005eae2ce82e0eb0dc8c63f034c.png

18- Tests d'intégration liés à l'authentification

  • Faire les specs couvrant l'authentification / autorisation / rĂ©vocation (auth.spec.js + device.spec.js)

19- Preloading

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.

  1. Créer "core/selective-preloading-strategy", cf https://angular.io/docs/ts/latest/guide/router.html#custom-preloading-strategy

  2. Ajouter la stratégie dans "app-routing.module.ts"

  3. 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 chose chance d'attĂ©rir sur l'inscription, c'est pour ça que l'on peut se permettre de preloaded.

20- Vue de connexion

Pour connecter l'utilisateur, on va créer un service "core/auth.service.ts".

Voici le déroulement général de l'authentification :

  1. Rediriger le rider sur la vue de connexion aprĂšs inscription

  2. Se connecter

  3. 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)

  4. 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

  5. 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

  6. A chaque changement de vue, vérifier si JWT présent, si non rediriger sur vue de connexion

[En cours]

21- Tableau de bord

  1. Créer feature "dashboard"

  2. 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.

  3. 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.

  4. Les gardes "CanActivate" et "CanLoad" seront ajoutés devant les portes de chaque feature nécessitant l'authentification.

[En cours]

Notes

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)

Dev tips

  • 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 sh $ npm install -g npm-check-updates
    • Comparer les version actuelles (fait un "npm outdated" en gros) sh $ ncu
    • Upgrade les versions actuelles sh $ ncu -u
  • 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

Liens utiles

Arborescence

Bases de données

Authentification

Map

Angular 2

Routing

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

Auth

http://jasonwatmore.com/post/2016/09/29/angular-2-user-registration-and-login-example-tutorial

A savoir

Liens plugins / packages

Auteur

Louis Grenard : https://www.louistiti.fr

twitter

Licence

MIT License

Copyright (c) 2016 Louistiti louis.grenard@gmail.com

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.

Cheers !

Cheers !