Skip to content
Snippets Groups Projects
Commit 36042f99 authored by Guillaume WANG's avatar Guillaume WANG
Browse files

changed stuff

parent 515c6202
No related branches found
No related tags found
No related merge requests found
...@@ -115,15 +115,22 @@ Pour accéder à la "vraie" BDD, sur roued (le serveur qui héberge sigma), il f ...@@ -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` * se connecter à la BDD via la commande `psql`
* faire les requetes en SQL par l'interface de postgreSQL. * faire les requetes en SQL par l'interface de postgreSQL.
## API/panneau d'administration ## Panneau d'administration
### Authentification ### 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 | ### Accès direct à la BDD via knex
| ------------------------------------ | ---------------------- |
| Renvoie un message de succès/d'échec | Redirige vers `/admin` | 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 ## Scripts
......
...@@ -5,20 +5,20 @@ import path from 'path'; ...@@ -5,20 +5,20 @@ import path from 'path';
/** /**
* @description Configuration de l'authentification * @description Configuration de l'authentification
* @author guillaume.wang * @author kadabra
* *
* on a besoin d'authentification pour 2 trucs : * on a besoin d'authentification pour 2 trucs :
* - l'acces a l'interface admin (admin_view) du back, definie dans admin_router.js * - l'acces a l'interface admin (admin_view) du back, definie dans admin_router.js
* - le contexte graphQL * - le contexte graphQL
* *
* serializeUser et deserializeUser: passport s'attend a ce qu'on ait besoin d'avoir req.user disponible partout dans notre code * serializeUser permet d'obtenir une cle identifiant chaque user
* En gros l'idee de passport c'est: 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
* 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
* 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 * 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 * cf https://stackoverflow.com/questions/27637609/understanding-passport-serialize-deserialize#27637668
* et http://toon.io/understanding-passportjs-authentication-flow/ * 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) * 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 configPath = path.resolve('./', 'ldap_config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
......
...@@ -2,16 +2,20 @@ ...@@ -2,16 +2,20 @@
* @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} en y ajoutant le routeur d'URL.
* @author manifold * @author manifold
*/ */
import server from './server'; import app from './server';
import colors from 'colors'; import colors from 'colors';
import router from './routing/admin_router'; import router from './routing/admin_router';
import passport from 'passport'; import passport from 'passport';
// setting up l'interface admin des BDD // 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; let port = process.env.PORT || 3000;
server.listen(port, () => { app.listen(port, () => {
console.log(colors.blue(`Express server listening on port ${port}`)); console.log(colors.blue(`Express server listening on port ${port}`));
}); });
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* permettant de consulter la base de donnée interne à Sigma, via des requêtes construites avec Knex. * permettant de consulter la base de donnée interne à Sigma, via des requêtes construites avec Knex.
* @author manifold * @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(). * mais ca marche visiblement pas... Donc j'ai mis les paths absolus dans les res.redirect().
*/ */
import express from 'express'; import express from 'express';
...@@ -23,20 +23,22 @@ let port = process.env.PORT || 3000; ...@@ -23,20 +23,22 @@ let port = process.env.PORT || 3000;
*/ */
router.get('/', function (req, res) { 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('Connecting to ' + req.url);
console.log('Trying to go to admin page...'); 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); console.log('Connecting to ' + req.url);
res.render('login', { title: 'Login', port: port, res.render('login', {
errorMessage: req.flash('error') }); //lets pug render src/views/login.pug with specified attributes title: 'Login', port: port,
errorMessage: req.flash('error')
}); //lets pug render src/views/login.pug with specified attributes
}); });
router.get('/admin', router.get('/admin',
ensureLoggedIn('/login'), ensureLoggedIn('/adminview/avlogin'),
function (req, res) { function (req, res) {
console.log('Connecting to ' + req.url); console.log('Connecting to ' + req.url);
let userName; let userName;
...@@ -46,7 +48,7 @@ router.get('/admin', ...@@ -46,7 +48,7 @@ router.get('/admin',
try { try {
let user = req.user; let user = req.user;
//let user = req.user; //let user = req.user;
console.log('Welcome,',user.uid); console.log('Welcome,', user.uid);
userName = user.uid; userName = user.uid;
} catch (err) { } catch (err) {
console.log("Warning: in admin_router router.get('/admin')"); console.log("Warning: in admin_router router.get('/admin')");
...@@ -57,9 +59,34 @@ 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(); req.logout();
res.redirect('/'); res.redirect('/adminview');
}); });
// je pense qu'on ferait mieux d'utiliser ca // je pense qu'on ferait mieux d'utiliser ca
...@@ -68,7 +95,7 @@ router.get('/db?', function (req, res) { ...@@ -68,7 +95,7 @@ router.get('/db?', function (req, res) {
let table_name = req.query.table; let table_name = req.query.table;
let columns = req.query.columns; 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) { ...@@ -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 * @function Error 404 catcher
......
/**
* @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;
...@@ -7,13 +7,11 @@ ...@@ -7,13 +7,11 @@
*/ */
import express from 'express'; import express from 'express';
import cookieParser from 'cookie-parser'; import cookieParser from 'cookie-parser';
import schema from './graphql/schema';
import { express as graphqlVoyager } from 'graphql-voyager/middleware'; 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'. cf npmjs.com
import flash from 'connect-flash';
import { ensureLoggedIn } from 'connect-ensure-login'; import { ensureLoggedIn } from 'connect-ensure-login';
import flash from 'connect-flash'; // utilise pour admin_view
import passport from 'passport'; import passport from 'passport';
import './auth';
import session from 'express-session'; import session from 'express-session';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import favicon from 'serve-favicon'; import favicon from 'serve-favicon';
...@@ -22,48 +20,80 @@ import path from 'path'; ...@@ -22,48 +20,80 @@ import path from 'path';
import fs from 'fs'; import fs from 'fs';
import cors from 'cors'; 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 // Parse incoming HTTP request bodies, available under the req.body property
// cf www.npmjs.com/package/body-parser // cf www.npmjs.com/package/body-parser
server.use(bodyParser.json()); //parses bodies of media type "application/json" app.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.urlencoded({ //parses bodies of media type "application/x-www-form-urlencoded"
extended: true //use qs library (quoi que ca veuille dire o.O) 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 configPath = path.resolve('./', 'ldap_config.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); 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. // Définit les paramètres de stockage des sessions.
server.use(session({ app.use(session({
secret: config.sessionSecret, secret: config.sessionSecret,
resave: true, resave: true,
saveUninitialized: false saveUninitialized: false
})); }));
server.use(passport.initialize()); app.use(passport.initialize());
server.use(passport.session()); 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. // 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 // setting up view engine for pug
console.log("Running at",__dirname); console.log("Running at",__dirname);
let viewpath = path.resolve(__dirname,'views'); let viewpath = path.resolve(__dirname,'views');
server.set('views', viewpath); app.set('views', viewpath);
server.set('view engine', 'pug'); app.set('view engine', 'pug');
// favicon: capital sigma symbol // 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 // 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 // Morgan is middleware for logging requests
server.use(morgan('dev')); app.use(morgan('dev'));
const defaultUser = require('./../ldap_connexion_config.json'); const defaultUser = require('./../ldap_connexion_config.json');
...@@ -73,7 +103,7 @@ const corsOptions = { ...@@ -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) 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 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'; import jwt from 'jsonwebtoken';
...@@ -93,10 +123,10 @@ const addUser = async (req, res, next) => { ...@@ -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 bodyParser.json(), // parse incoming HTTP request (req) as a JSON
graphqlHTTP(async (req, res, params) => { graphqlHTTP(async (req, res, params) => {
// vary the options *on a per-request basis* // vary the options *on a per-request basis*
...@@ -131,13 +161,13 @@ server.use('/graphql', ...@@ -131,13 +161,13 @@ server.use('/graphql',
); );
// GraphQL voyager affiche une représentation sous forme de graphe du schema GraphQL // GraphQL voyager affiche une représentation sous forme de graphe du schema GraphQL
server.use('/voyager', app.use('/voyager',
/*ensureLoggedIn('/login'),*/ /*ensureLoggedIn('/login'),*/
graphqlVoyager({ endpointUrl: '/graphql' }) graphqlVoyager({ endpointUrl: '/graphql' })
); );
// connect-flash is middleware for flashing messages // connect-flash is middleware for flashing messages
// used in sigma-back's admin interface // used in sigma-back's admin interface
server.use(flash()); app.use(flash());
export default server; export default app;
...@@ -6,4 +6,4 @@ block extraStyles ...@@ -6,4 +6,4 @@ block extraStyles
block content block content
h1 Error #{status} h1 Error #{status}
p #{error_message} p #{error_message}
a(href="/admin") Go back a(href="/adminview/admin") Go back
\ No newline at end of file \ No newline at end of file
...@@ -5,7 +5,7 @@ block content ...@@ -5,7 +5,7 @@ block content
h2 Query the database h2 Query the database
p Hello, world! This is server talking to you live on port #{port}! 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. 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 div.form-group
label(for="table") Table label(for="table") Table
input.form-control(type="search", name="table") input.form-control(type="search", name="table")
......
...@@ -6,7 +6,7 @@ block content ...@@ -6,7 +6,7 @@ block content
p Veuillez vous connecter. p Veuillez vous connecter.
p <em>Please log in.</em> p <em>Please log in.</em>
p #{errorMessage} p #{errorMessage}
form(action="/login", method="post") form(action="/adminview/login", method="post")
div.form-group div.form-group
label(for="username") Frankiz ID label(for="username") Frankiz ID
input.form-control(type="text", placeholder="User", name="username") input.form-control(type="text", placeholder="User", name="username")
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment