/** * @file Initialise et configure le serveur Express sur lequel tourne le back. * * La configuration inclut tout le _middleware_ définissant les API et les services * nécessaire utilisés, comme `express-session`, GraphiQL, GraphQL Voyager. * * @todo changer cette description... ^ * @todo 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 dotenv from 'dotenv'; dotenv.config(); import express from 'express'; import bodyParser from 'body-parser'; // packages pour graphql import { express as graphqlVoyager } from 'graphql-voyager/middleware'; // replacement of express-graphql, which hasn't been updated in 6 months import { ApolloServer } from 'apollo-server-express'; // typeDefs and resolvers import schema from './graphql/schema'; // packages pour adminview import { ensureLoggedIn } from 'connect-ensure-login'; import flash from 'connect-flash'; import router from './routing/admin.router'; // packages pour l'authentification import passport from 'passport'; import session from 'express-session'; 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 { ldapConfig, credentialsConfig } from './ldap/config'; const { dn, passwd } = credentialsConfig; // "The app object conventionally denotes the Express application" // see https://expressjs.com/en/4x/api.html#app const app = express(); // 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) })); //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. app.use(cookieParser()); // cache le fait que l'application tourne sous Express dans le header HTTP. app.disable('x-powered-by'); app.use(morgan('dev')); 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'))); /** * @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. */ /** * WTF??? why is sessionSecret in ldap_config.json? it has nothing to do with ldap. * @todo FIX */ /** /* 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 do this right, express-session docs warns that isn't for prod! * 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: ldapConfig.sessionSecret, resave: true, saveUninitialized: false, })); app.use(passport.initialize()); //GHETTO //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')) //this is equivalent to app.use(passport.authenticate('session')) app.use(passport.session(), (req, res, next)=>{ const user = req.user ? req.user.uid : "none"; console.log( `passport.session: found user: ${user}, authenticated: ${req.isAuthenticated()}`); next(); }); // *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... :/ // connect-flash is middleware for flashing messages, used in adminview app.use(flash()); /** * @desc SETUP DE ADMINVIEW */ // 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'); /** * @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 FRONTEND_SERVER_URL = process.env.FRONTEND_SERVER_URL || 'http://localhost:8888'; // Options de configuration pour le _middleware_ `cors`. // CORS = Cross Origin Resource Sharing const corsOptions = { origin: FRONTEND_SERVER_URL, // 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)); //GHETTO // Config de passport pour l'authentification ldap. Ne fait que *configurer* passport (aucun passport.authenticate() n'est appele, par exemple) import './config_passport'; //with custom callback: //http://www.passportjs.org/docs/authenticate/#custom-callback // http://toon.io/understanding-passportjs-authentication-flow/ app.post('/login', (req, res, next) => { console.log("Received an authentication request to /login"); passport.authenticate('ldapauth', (err, user, info) => { console.log("| Entering passport.authenticate('ldapauth', - ) callback"); // If an exception occurred if (err) { console.log("| Error when trying to passport.authenticate with ldapauth"); console.log(err); return res.status(err.status).json({ message: "Exception raised in backend process during authentication: " + err, authSucceeded: false }); // return next(err); // handle error? or drop request and answer with res.json()? } // If authentication failed, user will be set to false if (!user) { console.log("| Authentication failed, passport.authenticate did not return a user. "); return res.status(401).json({ message: "Authentication failed: " + info.message, authSucceeded: false }); } req.login(user, (err) => { // If an exception occurred at login if (err) { console.log("| Error when trying to req.login in callback in passport.authenticate('ldapauth', - )"); console.log(err); return res.status(err.status).json({ message: "Exception raised in backend process during login: " + err, authSucceeded: false }); // return next(err); // handle error? or drop request and answer with res.json()? } // If all went well console.log("| Authentication succeeded! :)"); // passport.authenticate automatically includes a Set-Cookie HTTP header in // the response. The JSON body is just to signal the frontend that all went well return res.status(200).json({ message: 'Authentication succeeded', authSucceeded: true }); }); })(req, res, next); }); //without custom callback: /* // http://toon.io/understanding-passportjs-authentication-flow/ app.post('/login', passport.authenticate('ldapauth'), function (req, res) { // If this function gets called, authentication was successful. // `req.user` contains the authenticated user. console.log("Frontend authentication succeeded"); res.json({ message: 'Authentication succeeded', authSucceeded: true }); } ); */ /** * @desc API GraphQL */ const environment = process.env.NODE_ENV || 'development'; /** * @desc Define GraphQL request context object, through a callback, with authorization. * See: https://github.com/apollographql/apollo-server/blob/master/docs/source/best-practices/authentication.md * */ const context = async ({ req }) => { let uid; let password; console.log("Responding to graphql request..."); console.log(` | User: ${req.user ? req.user.uid : "none"} | Authorization: ${req.headers.authorization} | Authenticated: ${req.isAuthenticated()} `.trim()); if(req.isAuthenticated()) { console.log("graphql API is receiving a request from an authenticated user! \\o/"); try { uid = req.user.uid; password = "mythe"; } catch (err) { console.log("Error: req is authenticated, but pb when trying to extract uid from req.user. Probably user was either not serialized or not deserialized properly"); console.log(err); } } else { // FOR DEVELOPMENT ONLY. for production, replace with a "publicUser" or "notLoggedInUser" or something. uid = dn.split("=")[1].split(",")[0]; password = passwd; } return { request: req, user: { uid, password } } } const server = new ApolloServer({ ...schema, context, playground: { settings: { "editor.theme": "dark", "editor.cursorShape": 'line' } } }); server.applyMiddleware({ app }); // GraphQL voyager affiche une représentation sous forme de graphe du schema GraphQL // accessible depuis adminview app.use('/voyager', /*ensureLoggedIn('/login'),*/ graphqlVoyager({ endpointUrl: '/graphql' }) ); // 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/*' app.use('/', (req, res) => { res.redirect('/adminview'); }); export default app;