Forked from an inaccessible project.
-
Guillaume WANG authoredGuillaume WANG authored
admin_router.ts 7.64 KiB
/**
* @file Ce fichier définit le routage d'URL au sein de l'interface du _backend_.
* Les URLs à résoudre ici sont les /adminview/* (et ce sont les seules).
*
* @desc Cette interface est destinée à n'être accessible qu'aux administrateurs du backend
* Filtrage basique : déjà il faut que la personne connaisse l'adresse IP ou le hostname du backend...
* L'authentification est basée sur passport avec ldapauth (comme pour les requêtes GraphQL), et
* l'autorisation se fait à la main en vérifiant l'appartenance de l'uid à une whitelist.
*
* Par cette interface admin, on a accès :
* - à une API REST fait-maison (peut-être encore à débugger),
* permettant de consulter la base de donnée interne à Sigma, via des requêtes construites avec Knex.
* accessible par les paths `/adminview/db/:table?`
* - à GraphQL Voyager, un package qui permet d'afficher une représentation sous forme de graphe du schéma GraphQL,
* acessible par le path `/adminview/voyager`
* - à GraphQL Playground, qui permet d'écrire ses propres requêtes GraphQL et de les exécuter.
* il est en fait accessible sans authentification du tout, au path `/graphql`, l'interface admin ne fait que fournir un lien vers ce path
* il est désactivé en production : "When NODE_ENV is set to production, GraphQL Playground (as well as introspection) is disabled as a production best-practice"
* https://www.apollographql.com/docs/apollo-server/features/graphql-playground.html#Configuring-Playground
*
* @author manifold, kadabra
*
* @todo whitelist the authorized admin uids; don't only use ensureLoggedIn
* @todo check that REST API is finished and functional
* @todo 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(). C'est un peu moche... essayer de modifier ca
*/
import { Router } from 'express';
// packages pour l'authentification
import passport from 'passport';
import { ensureLoggedIn } from 'connect-ensure-login';
import flash from 'connect-flash';
// packages pour l'API REST et pour GraphQL Voyager
import knex from '../../db/knex_router';
import { express as graphqlVoyager } from 'graphql-voyager/middleware';
const whitelist = [
"magi.karp",
"mew.two",
"lippou.tou",
"guillaume.wang",
]
/**
* @desc Création du Express router et setup de middlewares basiques
* =================================================================
* router: an Express router. https://expressjs.com/en/4x/api.html#router
* See also https://scotch.io/tutorials/learn-to-use-the-new-router-in-expressjs-4
* = a "sub-middleware stack", a “mini-application" inside the main application
*
* router automatically inherits from views and 'pug' view engine, that were set in app.ts (app.set(...))
*/
const router = Router();
router.use(flash());
let port = process.env.PORT || 3000;
/**
* @desc Paths pour l'authentification : login, logout, page d'accueil
* ===================================================================
* 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) {
res.redirect('/adminview/admin');
});
router.get('/avlogin', function (req, res) {
// lets pug render adminview/views/login.pug with specified attributes
res.render('login', {
title: 'Login', port: port,
errorMessage: req.flash('error')
});
});
router.get('/admin',
ensureLoggedIn('/adminview/avlogin'),
function (req, res) {
// 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
// (je suis à peu près sûr que ça ne peut jamais arriver ! pour moi si ça arrive c'est ultra bizarre et on ferait mieux de drop la requête. kadabra)
let userName;
if (req.user) {
userName = req.user.uid;
} else {
console.log("Warning: une requête est arrivée à /adminview/admin et req.user n'existe pas");
userName = "personne";
}
res.render('home', {
title: 'Home', port: port,
userName: userName
});
}
);
router.post('/avlogin',
passport.authenticate('ldapauth', {
failureRedirect: '/adminview/avlogin',
failureFlash: true
}),
(req, res) => {
// If this function gets called, authentication was successful.
// `req.user` contains the authenticated user.
// lookup req.user against whitelist of admin users
console.log("POST request to /adminview/avlogin: OK, authenticated by LDAP. req.user: ");
console.log(req.user);
if (req.user.uid in whitelist) {
console.log("is an admin");
res.redirect("/adminview/admin");
} else {
console.log("is NOT an admin");
req.flash('error: not an admin');
req.logout();
res.redirect('/adminview/admin');
}
}
);
router.post('/avlogout', function (req, res) {
req.logout();
res.redirect('/adminview/admin');
});
/**
* @desc GraphQL Voyager
* =====================
* affiche une représentation sous forme de graphe du schema GraphQL
*/
router.use('/voyager',
ensureLoggedIn('/login'),
graphqlVoyager({ endpointUrl: '/graphql' })
);
/**
* @desc API REST fait-maison
* ==========================
* `/admin/db/:table?columns=bla` fait une requete SELECT bla FROM table, et renvoie un JSON contenant le resultat
*/
router.use('/voyager',
ensureLoggedIn('/login'),
graphqlVoyager({ endpointUrl: '/graphql' })
);
// 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();
}
);
});
/**
* @desc Error handling
* ====================
*/
/**
* @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');
res.status(404);
next(err);
});
/**
* @function Error_404_handler
* @summary Gère les erreurs 404
*/
router.use((err, req, res, next) => {
console.log("adminview: 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;