From 36042f99a0942e6bcc60f96f9a71288dfe8fb9f3 Mon Sep 17 00:00:00 2001
From: Guillaume WANG <guillaume.wang@polytechnique.edu>
Date: Wed, 2 May 2018 02:55:14 +0200
Subject: [PATCH] changed stuff

---
 README.md                       |  17 +++-
 src/auth.js                     |   8 +-
 src/index.js                    |  10 +-
 src/routing/admin_router.js     |  73 ++++++++-------
 src/routing/admin_router_old.js | 158 ++++++++++++++++++++++++++++++++
 src/server.js                   |  76 ++++++++++-----
 src/views/error.pug             |   2 +-
 src/views/home.pug              |   2 +-
 src/views/login.pug             |   2 +-
 9 files changed, 275 insertions(+), 73 deletions(-)
 create mode 100644 src/routing/admin_router_old.js

diff --git a/README.md b/README.md
index cb465f7..9c1df86 100644
--- a/README.md
+++ b/README.md
@@ -115,15 +115,22 @@ Pour accéder à la "vraie" BDD, sur roued (le serveur qui héberge sigma), il f
 * se connecter à la BDD via la commande `psql`
 * faire les requetes en SQL par l'interface de postgreSQL.
 
-## API/panneau d'administration
+## Panneau d'administration
 
 ### Authentification
 
-L'authentification se fait contre le LDAP en envoyant un requête HTTP POST à '/login'. En fonction de la valeur du header `Accept` inclus dans la requête, on a deux comportements possibles
+L'authentification se fait contre le LDAP en envoyant un requête HTTP POST à '/adminview/avlogin'. C'est une page distincte de '/login' qui est utilisé pour les requetes d'authentification à partir du client front, pour pouvoir facilement distinguer les deux comportements :
+- login depuis le panneau d'administration (POST à '/adminview/avlogin') : `passport.authenticate(...)` puis redirection vers GET '/adminview/admin'
+- login depuis le client front (requête POST à '/login']) : `passport.authenticate(...)` puis renvoyer une reponse au client
 
-| `application/json`                   | autre                  |
-| ------------------------------------ | ---------------------- |
-| Renvoie un message de succès/d'échec | Redirige vers `/admin` |
+### Accès direct à la BDD via knex
+
+Le panneau d'administration sert (ou plutôt, servira à terme) à accéder directement à la BDD propre de sigma. On accède à la table `table_name` par une requête GET à '/adminview/db/`table_name`' et aux colonnes `columns` de cette table par une requête GET à '/adminview/db/`table_name`?columns=`columns`.
+Ces pages sont protégées pour n'être accessibles qu'en étant authentifé.
+
+### GraphiQL et Voyager
+
+A partir du panneau d'admin, en faisant des requêtes GET à '/graphiql' et '/voyager' respectivement, on accède à GraphiQL et à GraphQL Voyager. Ces pages sont protégées pour n'être accessibles qu'en étant authentifé.
 
 ## Scripts
 
diff --git a/src/auth.js b/src/auth.js
index 2c228d4..5e7d43e 100644
--- a/src/auth.js
+++ b/src/auth.js
@@ -5,20 +5,20 @@ import path from 'path';
 
 /**
  * @description Configuration de l'authentification
- * @author guillaume.wang
+ * @author kadabra
  *
  * on a besoin d'authentification pour 2 trucs :
  * - l'acces a l'interface admin (admin_view) du back, definie dans admin_router.js
  * - le contexte graphQL
  * 
- * serializeUser et deserializeUser: passport s'attend a ce qu'on ait besoin d'avoir req.user disponible partout dans notre code
- * En gros l'idee de passport c'est: serializeUser permet d'obtenir une cle identifiant chaque user
- * et deserializeUser prend cette cle, fait une requete vers une BDD de users et met dans l'objet JS req.user toutes les infos issues de la BDD
+ * serializeUser permet d'obtenir une cle identifiant chaque user
+ * deserializeUser fait une requete vers une BDD de users en utilisant cette cle, et met dans l'objet JS req.user toutes les infos issues de la BDD
  * Cette repartition permet de ne stocker dans la session (i.e. en memoire sur le serveur) que la cle des utilisateurs connectes et de ne "charger en memoire" toutes les infos de la BDD que lorsque necessaire
  * cf https://stackoverflow.com/questions/27637609/understanding-passport-serialize-deserialize#27637668
  * et http://toon.io/understanding-passportjs-authentication-flow/
  * 
  * Mais en fait dans notre cas c'est graphql qui communique avec la BDD, donc on s'en fiche! On peut se contenter de dire a serializeUser et deserializeUser de ne s'occuper que du champ uid)
+ * (on pourrait penser que passer par deserializeUser permettrait de reduire le nombre d'interactions avec la BDD, mais en fait non car deserializeUser est appele *a chaque requete*)
  */
 const configPath = path.resolve('./', 'ldap_config.json');
 const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
diff --git a/src/index.js b/src/index.js
index 9204649..45114ba 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,16 +2,20 @@
  * @file Lance le serveur configuré dans {@link server.js} en y ajoutant le routeur d'URL.
  * @author manifold
  */
-import server from './server';
+import app from './server';
 import colors from 'colors';
 import router from './routing/admin_router';
 import passport from 'passport';
 
 // setting up l'interface admin des BDD
-server.use('/',router); // catches and resolves HTTP requests to paths '/*'
+app.use('/adminview',router); // catches and resolves HTTP requests to paths '/adminview/*'
+
+app.get('/', // catches all other GET requests
+    ((req, res, next) => res.redirect('/adminview'))
+);
 
 let port = process.env.PORT || 3000;
 
-server.listen(port, () => {
+app.listen(port, () => {
     console.log(colors.blue(`Express server listening on port ${port}`));
 });
diff --git a/src/routing/admin_router.js b/src/routing/admin_router.js
index 48c968f..cff8de3 100644
--- a/src/routing/admin_router.js
+++ b/src/routing/admin_router.js
@@ -4,7 +4,7 @@
  * permettant de consulter la base de donnée interne à Sigma, via des requêtes construites avec Knex.
  * @author manifold
  * 
- * Les res.redirect() sont censes supporter les paths relatifs (et donc pas besoin de repreciser /* a chaque fois)
+ * Les res.redirect() sont censes supporter les paths relatifs (et donc pas besoin de repreciser /adminview/* a chaque fois)
  * mais ca marche visiblement pas... Donc j'ai mis les paths absolus dans les res.redirect().
  */
 import express from 'express';
@@ -23,20 +23,22 @@ let port = process.env.PORT || 3000;
  */
 
 router.get('/', function (req, res) {
-    console.log("GET handler for / route");
+    console.log("GET handler for /adminview route");
     console.log('Connecting to ' + req.url);
     console.log('Trying to go to admin page...');
-    res.redirect('/admin'); 
+    res.redirect('/adminview/admin');
 });
 
-router.get('/login', function (req, res) {
+router.get('/avlogin', function (req, res) {
     console.log('Connecting to ' + req.url);
-    res.render('login', { title: 'Login', port: port, 
-        errorMessage: req.flash('error') }); //lets pug render src/views/login.pug with specified attributes
+    res.render('login', {
+        title: 'Login', port: port,
+        errorMessage: req.flash('error')
+    }); //lets pug render src/views/login.pug with specified attributes
 });
 
 router.get('/admin',
-    ensureLoggedIn('/login'),
+    ensureLoggedIn('/adminview/avlogin'),
     function (req, res) {
         console.log('Connecting to ' + req.url);
         let userName;
@@ -46,7 +48,7 @@ router.get('/admin',
         try {
             let user = req.user;
             //let user = req.user;
-            console.log('Welcome,',user.uid);
+            console.log('Welcome,', user.uid);
             userName = user.uid;
         } catch (err) {
             console.log("Warning: in admin_router router.get('/admin')");
@@ -57,9 +59,34 @@ router.get('/admin',
     }
 );
 
-router.post('/logout', function (req, res) {
+router.post('/avlogin',
+    passport.authenticate('ldapauth', {
+        successRedirect: '/adminview/admin',
+        failureRedirect: '/adminview/avlogin',
+        failureFlash: true
+    }
+        // on a besoin de faire un callback apres le passport.authenticate car
+        // on souhaite garde l'information user.dn et body.password qq part.
+        // TODO: essayer de garder ces informations plus proprement...
+        // EDIT: en fait apparemment on a pas besoin de ces informations du tout
+        /*
+        function (req, res) {
+            req.session.dn = req.user.dn;
+            req.session.password = req.body.password;
+    
+            if (req.session.returnTo !== undefined) {
+                res.redirect(req.session.returnTo); //TODO: <- euh ok ca marche mais c'est quoi ca?
+            } else {
+                res.redirect("/admin");
+            }
+        }
+        */
+    )
+);
+
+router.post('/avlogout', function (req, res) {
     req.logout();
-    res.redirect('/');
+    res.redirect('/adminview');
 });
 
 // je pense qu'on ferait mieux d'utiliser ca
@@ -68,7 +95,7 @@ router.get('/db?', function (req, res) {
     let table_name = req.query.table;
     let columns = req.query.columns;
 
-    res.redirect(`/db/${table_name}?columns=${columns}`);
+    res.redirect(`/adminview/db/${table_name}?columns=${columns}`);
 });
 
 /**
@@ -102,30 +129,6 @@ router.get('/db/:table_name?', function (req, res) {
     );
 });
 
-router.post('/login', (req, res, next) => {
-    passport.authenticate('ldapauth', (err, user, info) => {
-        if (err) return next(err); // handle error
-        else if (!user) {
-            return res.json(403, {message: "Not authenticated."});
-        }
-        
-        req.login(user, (err) => {
-            if (err) {
-                console.log(err);
-                return err;
-            }
-
-            // if request asks for a json, reply with a token
-            // else redirect to admin panel
-            (req.header('accept') == 'application/json')
-                ? res.json({ 
-                    message: 'Authentication succeeded.'
-                })
-                : res.redirect('/admin');
-            
-        });
-    })(req,res,next);
-});
 
 /**
  * @function Error 404 catcher
diff --git a/src/routing/admin_router_old.js b/src/routing/admin_router_old.js
new file mode 100644
index 0000000..1c198e1
--- /dev/null
+++ b/src/routing/admin_router_old.js
@@ -0,0 +1,158 @@
+/**
+ * @file Ce fichier définit le routage d'URL au sein de l'interface du _backend_.
+ * Il définit la page de connexion `/`, le panneau administrateur `/admin` et l'API REST \(`/db/:table?`)
+ * permettant de consulter la base de donnée interne à Sigma, via des requêtes construites avec Knex.
+ * @author manifold
+ * 
+ * Les res.redirect() sont censes supporter les paths relatifs (et donc pas besoin de repreciser /adminview/* a chaque fois)
+ * mais ca marche visiblement pas... Donc j'ai mis les paths absolus dans les res.redirect().
+ */
+import express from 'express';
+import knex from '../../db/knex_router';
+import passport from 'passport';
+import { ensureLoggedIn } from 'connect-ensure-login';
+
+const router = express.Router();
+
+let port = process.env.PORT || 3000;
+
+
+/**
+ * @description Le login se fait en POST. Faire un GET à la racine / renvoie sur
+ * /login ou sur /admin selon que l'utilisateur est connecté ou non.
+ */
+
+router.get('/', function (req, res) {
+    console.log("GET handler for /adminview route");
+    console.log('Connecting to ' + req.url);
+    console.log('Trying to go to admin page...');
+    res.redirect('/adminview/admin'); 
+});
+
+router.get('/avlogin', function (req, res) {
+    console.log('Connecting to ' + req.url);
+    res.render('login', { title: 'Login', port: port, 
+        errorMessage: req.flash('error') }); //lets pug render src/views/login.pug with specified attributes
+});
+
+router.get('/admin',
+    ensureLoggedIn('/adminview/avlogin'),
+    function (req, res) {
+        console.log('Connecting to ' + req.url);
+        let userName;
+        // Une erreur a ce stade peut etre triggered si req.user n'existe pas
+        // mais pour autant on est assures que la personne est bien authentifiee
+        // donc on laisse passer sans déclencher d'erreur 500
+        try {
+            let user = req.user;
+            //let user = req.user;
+            console.log('Welcome,',user.uid);
+            userName = user.uid;
+        } catch (err) {
+            console.log("Warning: in admin_router router.get('/admin')");
+            console.log(err.message);
+            userName = "No one";
+        }
+        res.render('home', { title: 'Home', port: port, userName: userName });
+    }
+);
+
+router.post('/avlogin', 
+    passport.authenticate('ldapauth', {
+        successRedirect: '/adminview/admin',
+        failureRedirect: '/adminview/avlogin',
+        failureFlash: true
+    }
+    // on a besoin de faire un callback apres le passport.authenticate car
+    // on souhaite garde l'information user.dn et body.password qq part.
+    // TODO: essayer de garder ces informations plus proprement...
+    // EDIT: en fait apparemment on a pas besoin de ces informations du tout
+    /*
+    function (req, res) {
+        req.session.dn = req.user.dn;
+        req.session.password = req.body.password;
+
+        if (req.session.returnTo !== undefined) {
+            res.redirect(req.session.returnTo); //TODO: <- euh ok ca marche mais c'est quoi ca?
+        } else {
+            res.redirect("/admin");
+        }
+    }
+    */
+    )
+);
+
+router.post('/avlogout', function (req, res) {
+    req.logout();
+    res.redirect('/adminview');
+});
+
+// je pense qu'on ferait mieux d'utiliser ca
+// https://expressjs.com/en/4x/api.html#router.route
+router.get('/db?', function (req, res) {
+    let table_name = req.query.table;
+    let columns = req.query.columns;
+
+    res.redirect(`/adminview/db/${table_name}?columns=${columns}`);
+});
+
+/**
+ * @function Knex API: Get table
+ * @summary Effectue une requête pour une table dans la BDD
+ * @argument {string} table_name - La table voulue par l'utilisateur.
+ */
+router.get('/db/:table_name?', function (req, res) {
+
+    // get columns from query
+    let columns;
+    if (req.query.columns) {
+        columns = req.query.columns.split(',');
+    } else {
+        columns = null;
+    }
+    console.log(columns);
+
+    knex.select(columns).from(req.params.table_name).then(function (table) {
+        res.setHeader("Content-Type", "application/json");
+        res.write(JSON.stringify(table, null, 2));
+        res.end();
+    }, function () {
+        res.status(400);
+        res.render('error', {
+            status: res.statusCode,
+            error_message: "Bad request: can't find table " + req.params.table_name
+        });
+        res.end();
+    }
+    );
+});
+
+/**
+ * @function Error 404 catcher
+ * @summary Catche les requêtes en dehors des URL acceptées
+ */
+router.use((req, res, next) => {
+    let err = new Error('Not found');
+    err.status = 404;
+    next(err);
+});
+
+/**
+ * @function Error 404 handler
+ * @summary Gère les erreurs 404
+ */
+router.use((err, req, res, next) => {
+    console.log("Entering error handler");
+    res.locals.message = err.message;
+    console.log(err.message);
+
+    res.status(err.status || 500);
+    let error_message = res.statusCode == 404 ? 'Not found.' : 'Internal server error.';
+    res.render('error', {
+        status: res.statusCode,
+        error_message: error_message
+    });
+});
+
+export default router;
+
diff --git a/src/server.js b/src/server.js
index 38969aa..efc9658 100644
--- a/src/server.js
+++ b/src/server.js
@@ -7,13 +7,11 @@
 */
 import express from 'express';
 import cookieParser from 'cookie-parser';
-import schema from './graphql/schema';
 import { express as graphqlVoyager } from 'graphql-voyager/middleware';
 import graphqlHTTP from 'express-graphql'; // new name of 'graphql-server-express'. cf npmjs.com
-import flash from 'connect-flash';
 import { ensureLoggedIn } from 'connect-ensure-login';
+import flash from 'connect-flash'; // utilise pour admin_view
 import passport from 'passport';
-import './auth';
 import session from 'express-session';
 import bodyParser from 'body-parser';
 import favicon from 'serve-favicon';
@@ -22,48 +20,80 @@ import path from 'path';
 import fs from 'fs';
 import cors from 'cors';
 
-const server = express();
+import schema from './graphql/schema';
+import './auth';
+
+const app = express(); 
+//The app object conventionally denotes the Express application 
 
 // Parse incoming HTTP request bodies, available under the req.body property
 // cf www.npmjs.com/package/body-parser
-server.use(bodyParser.json()); //parses bodies of media type "application/json"
-server.use(bodyParser.urlencoded({ //parses bodies of media type "application/x-www-form-urlencoded"
+app.use(bodyParser.json()); //parses bodies of media type "application/json"
+app.use(bodyParser.urlencoded({ //parses bodies of media type "application/x-www-form-urlencoded"
     extended: true //use qs library (quoi que ca veuille dire o.O)
 }));
-server.use(cookieParser());
+app.use(cookieParser());
 
 const configPath = path.resolve('./', 'ldap_config.json');
 const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
 
+/* CONFIGURATION DE L'AUTHENTIFICATION*/
+
+// Config de passport : le "import './auth';" plus haut execute la configuration de l'objet passport. cf, donc, auth.js
+
 // Définit les paramètres de stockage des sessions.
-server.use(session({
+app.use(session({
     secret: config.sessionSecret,
     resave: true,
     saveUninitialized: false
 }));
-server.use(passport.initialize());
-server.use(passport.session());
+app.use(passport.initialize());
+app.use(passport.session());
+
+
+app.post('/login', (req, res, next) => {
+    passport.authenticate('ldapauth', (err, user, info) => {
+        if (err) return next(err); // handle error
+        else if (!user) {
+            return res.json(403, { message: "Not authenticated." });
+        }
+
+        req.login(user, (err) => {
+            if (err) {
+                console.log(err);
+                return err;
+            }
 
+            // if request asks for a json, reply with a token
+            // else redirect to admin panel
+            (req.header('accept') == 'application/json')
+                ? res.json({
+                    message: 'Authentication succeeded.'
+                })
+                : res.redirect('/admin');
 
-/* fin de Configuration de l'authentification */
+        });
+    })(req, res, next);
+});
+/* FIN DE CONFIG DE L'AUTH. */
 
 // cache le fait que l'application tourne sous Express dans le header HTTP.
-server.disable('x-powered-by');
+app.disable('x-powered-by');
 
 // setting up view engine for pug
 console.log("Running at",__dirname);
 let viewpath = path.resolve(__dirname,'views');
-server.set('views', viewpath);
-server.set('view engine', 'pug');
+app.set('views', viewpath);
+app.set('view engine', 'pug');
 
 // favicon: capital sigma symbol
-server.use(favicon(path.resolve('./','assets','favicon.ico')));
+app.use(favicon(path.resolve('./','assets','favicon.ico')));
 
 // specifies path to static assets
-server.use('/assets',express.static(path.resolve('./','assets')));
+app.use('/assets',express.static(path.resolve('./','assets')));
 
 // Morgan is middleware for logging requests
-server.use(morgan('dev'));
+app.use(morgan('dev'));
 
 const defaultUser = require('./../ldap_connexion_config.json');
 
@@ -73,7 +103,7 @@ const corsOptions = {
     origin: 'http://localhost:8888', // Configures the Access-Control-Allow-Origin CORS header. i.e. specifies that sigma-back wants to make resources accessible to this site (and this site only)
     credentials: true // Configures the Access-Control-Allow-Credentials CORS header. i.e. allows cookies to be included on cross-origin requests
 };
-server.use(cors(corsOptions));
+app.use(cors(corsOptions));
 
 import jwt from 'jsonwebtoken';
 
@@ -93,10 +123,10 @@ const addUser = async (req, res, next) => {
     }
 };
 
-server.use(addUser);
+app.use(addUser);
 */
 
-server.use('/graphql', 
+app.use('/graphql', 
     bodyParser.json(), // parse incoming HTTP request (req) as a JSON
     graphqlHTTP(async (req, res, params) => {
         // vary the options *on a per-request basis*
@@ -131,13 +161,13 @@ server.use('/graphql',
 );
 
 // GraphQL voyager affiche une représentation sous forme de graphe du schema GraphQL
-server.use('/voyager',
+app.use('/voyager',
     /*ensureLoggedIn('/login'),*/
     graphqlVoyager({ endpointUrl: '/graphql' })
 );
 
 // connect-flash is middleware for flashing messages
 // used in sigma-back's admin interface
-server.use(flash());
+app.use(flash());
 
-export default server;
+export default app;
diff --git a/src/views/error.pug b/src/views/error.pug
index c551b32..31547c3 100644
--- a/src/views/error.pug
+++ b/src/views/error.pug
@@ -6,4 +6,4 @@ block extraStyles
 block content
     h1 Error #{status}
     p #{error_message}
-    a(href="/admin") Go back
\ No newline at end of file
+    a(href="/adminview/admin") Go back
\ No newline at end of file
diff --git a/src/views/home.pug b/src/views/home.pug
index e0ab000..f92c929 100644
--- a/src/views/home.pug
+++ b/src/views/home.pug
@@ -5,7 +5,7 @@ block content
     h2 Query the database
     p Hello, world! This is server talking to you live on port #{port}!
     p You can use the REST API to query the database using the form below.
-    form(action="/db", method="get")
+    form(action="/adminview/db", method="get")
         div.form-group
             label(for="table") Table
             input.form-control(type="search", name="table")
diff --git a/src/views/login.pug b/src/views/login.pug
index f0594a3..d847d01 100644
--- a/src/views/login.pug
+++ b/src/views/login.pug
@@ -6,7 +6,7 @@ block content
     p Veuillez vous connecter.
     p <em>Please log in.</em>
     p #{errorMessage}
-    form(action="/login", method="post")
+    form(action="/adminview/login", method="post")
         div.form-group
             label(for="username") Frankiz ID
             input.form-control(type="text", placeholder="User", name="username")
-- 
GitLab