From ba7af69338aad1927051d05c1d433a5ff090c6c8 Mon Sep 17 00:00:00 2001 From: Guillaume WANG <guillaume.wang@polytechnique.edu> Date: Fri, 4 May 2018 00:50:31 +0200 Subject: [PATCH] reorganized all of middleware stack --- src/{routing => admin_view}/admin_router.js | 7 + src/{auth.js => config_passport.js} | 6 +- src/index.js | 10 +- src/routing/admin_router_old.js | 158 ---------------- src/server.js | 194 +++++++++++++------- 5 files changed, 141 insertions(+), 234 deletions(-) rename src/{routing => admin_view}/admin_router.js (99%) rename src/{auth.js => config_passport.js} (94%) delete mode 100644 src/routing/admin_router_old.js diff --git a/src/routing/admin_router.js b/src/admin_view/admin_router.js similarity index 99% rename from src/routing/admin_router.js rename to src/admin_view/admin_router.js index cff8de3..9248f12 100644 --- a/src/routing/admin_router.js +++ b/src/admin_view/admin_router.js @@ -89,6 +89,10 @@ router.post('/avlogout', function (req, res) { 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) { @@ -130,6 +134,9 @@ router.get('/db/:table_name?', function (req, res) { }); + + + /** * @function Error 404 catcher * @summary Catche les requêtes en dehors des URL acceptées diff --git a/src/auth.js b/src/config_passport.js similarity index 94% rename from src/auth.js rename to src/config_passport.js index 424bca8..6ba838b 100644 --- a/src/auth.js +++ b/src/config_passport.js @@ -4,7 +4,7 @@ import fs from 'fs'; import path from 'path'; /** - * @description Configuration de l'authentification + * @description Configuration de passport pour utiliser l'authentification LDAP * @author kadabra * * on a besoin d'authentification pour 2 trucs : @@ -23,6 +23,7 @@ import path from 'path'; const configPath = path.resolve('./', 'ldap_config.json'); const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); +// specifies options for 'ldapauth' strategy, to customize the behaviour of subsequent passport.authenticate('ldapauth') calls passport.use(new LdapStrategy({ server: { url: config.ldap.server, @@ -56,8 +57,7 @@ passport.use(new LdapStrategy({ } } */ -}) -); +})); //toujours bon a savoir pour faire des tests: diff --git a/src/index.js b/src/index.js index 45114ba..ce145b2 100644 --- a/src/index.js +++ b/src/index.js @@ -1,19 +1,11 @@ /** - * @file Lance le serveur configuré dans {@link server.js} en y ajoutant le routeur d'URL. + * @file Lance le serveur configuré dans {@link server.js} * @author manifold */ 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 -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; app.listen(port, () => { diff --git a/src/routing/admin_router_old.js b/src/routing/admin_router_old.js deleted file mode 100644 index 1c198e1..0000000 --- a/src/routing/admin_router_old.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * @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 c68ea1c..61a71ed 100644 --- a/src/server.js +++ b/src/server.js @@ -3,108 +3,125 @@ * * La configuration inclut tout le _middleware_ définissant les API et les services * nécessaire utilisés, comme `express-session`, GraphiQL, GraphQL Voyager. - * @author manifold + * + * TODO: changer cette description... ^ + * TODD: qu'arrive-t-il aux requetes avec un cookie expire? elles ne sont traitees ni par passport.session() ni par passport.authenticate('ldapauth')... + * + * @author manifold, kadabra */ + import express from 'express'; -import cookieParser from 'cookie-parser'; +import bodyParser from 'body-parser'; +// packages pour graphql import { express as graphqlVoyager } from 'graphql-voyager/middleware'; -import graphqlHTTP from 'express-graphql'; // new name of 'graphql-server-express'. cf npmjs.com +import graphqlHTTP from 'express-graphql'; // new name of 'graphql-server-express' (mai 2018). cf npmjs.com. +import schema from './graphql/schema'; +// packages pour adminview import { ensureLoggedIn } from 'connect-ensure-login'; -import flash from 'connect-flash'; // utilise pour admin_view +import flash from 'connect-flash'; +import router from './admin_view/admin_router'; +// packages pour l'authentification import passport from 'passport'; import session from 'express-session'; -import bodyParser from 'body-parser'; +import cookieParser from 'cookie-parser'; +import cors from 'cors'; +// packages divers import favicon from 'serve-favicon'; import morgan from 'morgan'; +// packages pour pouvoir importer depuis des fichiers de config import path from 'path'; import fs from 'fs'; -import cors from 'cors'; -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 +const app = express(); // "The app object conventionally denotes the Express application" (https://expressjs.com/en/4x/api.html#app) + +// Parse incoming HTTP request bodies, available under the req.body property. cf www.npmjs.com/package/body-parser 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) })); -app.use(cookieParser()); + + + +/** + * @desc TRUCS DIVERS + */ + +// cache le fait que l'application tourne sous Express dans le header HTTP. +app.disable('x-powered-by'); +// Morgan is middleware for logging requests +app.use(morgan('dev')); +// favicon: capital sigma symbol +app.use(favicon(path.resolve('./', 'assets', 'favicon.ico'))); +// specifies path to static assets. ......je comprends pas ce que c'est. TODO +app.use('/assets', express.static(path.resolve('./', 'assets'))); + +/** + * FIN TRUCS DIVERS + */ + + + + + +/** + * @desc AUTHENTIFICATION POUR LES REQUETES POSSEDANT UN COOKIE ET PROVENANT D'UN UTILISATEUR DEJA AUTHENTIFIE + * Remarque: introduit aussi les middlewares session et passport, qui sont aussi utiles pour l'authentification dans les autres cas. + */ const configPath = path.resolve('./', 'ldap_config.json'); const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); +// WTF??? why is sessionSecret in ldap_config.json? it has nothing to do with ldap. TODO -// Config de passport : le "import './auth';" plus haut execute la configuration de l'objet passport. cf, donc, auth.js +//app.use(cookieParser()); //parses Cookie header and populate req.cookies with an object keyed by the cookie names. was necessary for express-session before its v1.5.0. on peut probablement l'enlever desormais. -// Définit les paramètres de stockage des sessions. +// defines parameters for *session store*. (adds field req.session and do some magic stuff) +// basically, searches for a session matching the received cookie and, if found, adds field req.blasomethingbla containing serialized object representing user (i.e. similar to what passport.serializeUser() could produce) +// TODO: it is important to configure this right!!! please check out https://www.npmjs.com/package/express-session and make sure you understand the way session is stored. (en vrai c'est vraiment important...) app.use(session({ secret: config.sessionSecret, resave: true, - saveUninitialized: false + saveUninitialized: false, + //store: // TODO: change this. express-session doc warns that default value is ok to use for development only })); -app.use(passport.initialize()); -app.use(passport.session()); +app.use(passport.initialize()); //initialize Passport. (adds hidden field req._passport and do some magic stuff) +app.use(passport.session()); //this is equivalent to app.use(passport.authenticate('session')) +// *aucun* effet sur les requetes n'ayant pas ete reconnues par app.use(session(...)) (e.g. les requetes sans cookie ou les requetes avec cookie expired). source: lecture directe du code passport/lib/strategies/session.js sur github... :/ +/** + * FIN AUTHENTIFICATION POUR LES REQUETES POSSEDANT UN COOKIE ET PROVENANT D'UN UTILISATEUR DEJA AUTHENTIFIE + */ -// cache le fait que l'application tourne sous Express dans le header HTTP. -app.disable('x-powered-by'); -// setting up view engine for pug -console.log("Running at",__dirname); -let viewpath = path.resolve(__dirname,'views'); -app.set('views', viewpath); -app.set('view engine', 'pug'); -// favicon: capital sigma symbol -app.use(favicon(path.resolve('./','assets','favicon.ico'))); -// specifies path to static assets -app.use('/assets',express.static(path.resolve('./','assets'))); -// Morgan is middleware for logging requests -app.use(morgan('dev')); +/** + * @desc AUTHENTIFICATION POUR LES REQUETES DE CONNEXION VIA LDAP VENANT DU FRONT + * i.e. endpoint for *frontend's* authentication requests (login through adminview/avlogin are caught by the router in admin_router.js) + * i.e. quand l'utilisateur submit le formulaire de login avec ses identifiants/mdp dans le front + * Remarque: configure aussi passport pour l'authentification ldap, ce qui est aussi utile pour les requetes de connexion via ldap venant de adminview + */ -const defaultUser = require('./../ldap_connexion_config.json'); +const FRONTEND_SERVER_URL = 'change this to frontend server IP address'; +const FRONTEND_SERVER_URL_LOCAL = 'http://localhost:8888'; // Options de configuration pour le _middleware_ `cors`. // CORS = Cross Origin Resource Sharing 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) + origin: FRONTEND_SERVER_URL_LOCAL, // 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 }; app.use(cors(corsOptions)); -const SECRET_KEY = "azojgc;aegpfrihzcksdlmpqsqkx"; +// Config de passport pour l'authentification ldap. Ne fait que *configurer* passport (aucun passport.authenticate() n'est appele, par exemple) +import './config_passport.js'; -/* -const addUser = async (req, res, next) => { - const token = req.cookies.csrftoken; - if (!token) return next(); - console.log(`Token is ${token}`); - - try { - const { user } = jwt.verify(token, SECRET_KEY); - req.user = user; - } catch (err) { - console.log('Cookie error',err); - } -}; - -app.use(addUser); -*/ - - - - -//endpoint for frontend's authentication requests //with custom callback: //http://www.passportjs.org/docs/authenticate/#custom-callback // http://toon.io/understanding-passportjs-authentication-flow/ - app.post('/login', (req, res, next) => { passport.authenticate('ldapauth', (err, user, info) => { // If an exception occurred @@ -118,15 +135,15 @@ app.post('/login', (req, res, next) => { } // If authentication failed, user will be set to false if (!user) { - return res.status(401).json({ + return res.status(401).json({ message: "Authentication failed: " + info.message, authSucceeded: false }); } - + req.login(user, (err) => { // If an exception occurred at login - if (err) { + if (err) { console.log(err); return res.status(err.status).json({ message: "Exception raised in backend process during login: " + err, @@ -160,6 +177,19 @@ app.post('/login', ); */ +/** + * FIN AUTHENTIFICATION POUR LES REQUETES DE CONNEXION VIA LDAP VENANT DU FRONT + */ + + + + + +/** + * @desc API GRAPHQL + */ + +import { dn, passwd } from "../ldap_connexion_config.json"; // default user app.use('/graphql', bodyParser.json(), // parse incoming HTTP request (req) as a JSON @@ -182,8 +212,8 @@ app.use('/graphql', } } else { // FOR DEVELOPMENT ONLY. for production, replace with a "publicUser" or "notLoggedInUser" or something. - uid = defaultUser.dn.split("=")[1].split(",")[0]; - password = defaultUser.passwd; + uid = dn.split("=")[1].split(",")[0]; + password = passwd; } console.log("Cookies:",req.cookies); @@ -196,14 +226,50 @@ app.use('/graphql', }) ); +/** + * FIN API GRAPHQL + */ + + + + + +/** + * @desc SETUP DE ADMINVIEW, L'INTERFACE ADMIN DES BDD + * Remarque: le graphiql est desormais integre a express-graphql (mai 2018), donc il n'est plus possible de le proteger par connect-ensure-login, ni donc de considerer qu'il fait partie de l'adminview. + * C'est relativement grave, car n'importe qui se connectant directement a [adresse_IP_de_roued]/graphql peut faire des requetes a la base de donnees de sigma + * Donc il faudra retirer cette fonctionnalite en production ; or elle est pratique, meme en production, pour des sanity checks. + * ...bref integrer graphiql a express-graphql etait completement con. + */ + +// setting up view engine for pug, for adminview +console.log("Running at", __dirname); +let viewpath = path.resolve(__dirname, 'views'); +app.set('views', viewpath); +app.set('view engine', 'pug'); + +// connect-flash is middleware for flashing messages, used in adminview +app.use(flash()); + // GraphQL voyager affiche une représentation sous forme de graphe du schema GraphQL +// accessible depuis adminview app.use('/voyager', /*ensureLoggedIn('/login'),*/ graphqlVoyager({ endpointUrl: '/graphql' }) ); -// connect-flash is middleware for flashing messages -// used in sigma-back's admin interface -app.use(flash()); +// on utilise un express.Router qui sert a creer un "sous-middleware stack". +app.use('/adminview', router); // catches and resolves HTTP requests to paths '/adminview/*' + +// catch all other GET requests. +// il est bien sur essentiel de mettre ceci a la toute fin du middleware stack ! +app.get('/*', + ((req, res, next) => res.redirect('/adminview')) +); + +/** + * FIN SETUP DE ADMINVIEW, L'INTERFACE ADMIN DES BDD + */ + export default app; -- GitLab