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

admin_router

parent 0aea7e1e
No related branches found
No related tags found
No related merge requests found
/**
* @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 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
*
* 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().
* @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';
import knex from '../../db/knex_router';
// packages pour l'authentification
import passport from 'passport';
import flash from 'connect-flash';
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';
// packages pour pouvoir importer des fichiers de config
import path from 'path';
import { static as expressStatic } from 'express'; // pour pouvoir servir les fichiers statiques
import favicon from 'serve-favicon'; // tres tres important :p
const whitelist = [
"magi.karp",
"mew.two",
"lippou.tou",
"guillaume.wang",
]
import favicon from 'serve-favicon';
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')));
// setting up view engine for pug, for adminview
console.log("Express app is running at", __dirname);
let viewpath = path.resolve(__dirname, 'views');
app.set('views', viewpath);
app.set('view engine', 'pug');
// GraphQL voyager affiche une représentation sous forme de graphe du schema GraphQL
// accessible depuis adminview
app.use('/voyager',
/*ensureLoggedIn('/login'),*/
graphqlVoyager({ endpointUrl: '/graphql' })
);
// router: an Express router. https://expressjs.com/en/4x/api.html#router
// = a "sub-middleware stack", a “mini-application" inside the main application
/**
* @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;
console.log("Express router is running at", __dirname);
router.use(favicon(path.resolve(__dirname, 'assets', 'favicon.ico')));
router.use('/assets', expressStatic(path.resolve(__dirname, 'assets')));
let port = process.env.PORT || 3000;
/**
* * @desc Le login se fait en POST. Faire un GET à la racine / renvoie sur
* @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.
*/
......@@ -53,64 +76,81 @@ router.get('/', function (req, res) {
});
router.get('/avlogin', function (req, res) {
// lets pug render src/views/login.pug with specified attributes
// lets pug render adminview/views/login.pug with specified attributes
res.render('login', {
title: 'Login', port: port,
errorMessage: req.flash('error') //hawkspar->manifold VSCode râle ici pr moi ; un commentaire ?
errorMessage: req.flash('error')
});
});
router.get('/admin',
ensureLoggedIn('/adminview/avlogin'),
function (req, res) {
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;
userName = user.uid;
} catch (err) {
userName = "No one";
let userName;
if (req.user) {
userName = req.user.uid;
} else {
console.log("Warning: une requete est arrivée /adminview/admin où req.user n'existe pas");
userName = "personne";
}
res.render('home', { title: 'Home', port: port, userName: userName });
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");
}
}),
(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');
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) {
......@@ -121,7 +161,7 @@ router.get('/db?', function (req, res) {
});
/**
* @function Knex API: Get table
* @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.
*/
......@@ -151,6 +191,10 @@ router.get('/db/:table_name?', function (req, res) {
);
});
/**
* @desc Error handling
* ====================
*/
/**
* @function Error_404_catcher
* @summary Catche les requêtes en dehors des URL acceptées
......
File moved
File moved
......@@ -24,4 +24,4 @@ block content
a(class="button button-small",href="/graphql") Playground
h2 GraphQL Voyager
p Voyager est un outil de visualisation du « graphe » derrière l'API.
a(class="button button-small",href="/voyager") Voyager
a(class="button button-small",href="/adminview/voyager") Voyager
......@@ -13,7 +13,6 @@
import express from 'express';
import bodyParser from 'body-parser';
// packages pour graphql
import { express as graphqlVoyager } from 'graphql-voyager/middleware';
import { ApolloServer } from 'apollo-server-express';
import schema from './graphql/schema'; // definition du schéma et des resolvers
// l'interface admin du backend (adminview)
......@@ -25,12 +24,10 @@ import cookieParser from 'cookie-parser';
import cors from 'cors';
// HTTP request logger
import morgan from 'morgan';
// packages pour pouvoir importer depuis des fichiers de config
// packages pour pouvoir importer des fichiers de config
import path from 'path';
// config des paramètres de connexion au LDAP
import { ldapConfig, credentialsLdapConfig } from './ldap/config';
const { dn, passwd } = credentialsLdapConfig;
// configure passport, pour l'authentification ldap et pour comment gérer les sessions (serializeUser/deserializeUser)
import './config_passport';
......@@ -49,7 +46,7 @@ app.use(bodyParser.urlencoded({ //parses bodies of media type "application/x-www
//app.use(cookieParser()); <-- req.cookies n'est pas utilisé.
// ne *pas* inclure de header HTTP publicisant que l'application tourne sous Express
app.disable('x-powered-by');
// setup morgan (HTTP request logger middleware)
// use morgan (HTTP request logger middleware)
app.use(morgan('dev'));
const FRONTEND_SERVER_URL = process.env.FRONTEND_SERVER_URL || 'http://localhost:8888';
......@@ -67,15 +64,17 @@ app.use(cors(corsOptions));
* @desc Authentification de la requête contre le session-store (cookie)
* =====================================================================
* Si la requête possède un cookie, on regarde dans la session s'il s'agit d'un cookie valide.
* - Si oui, on récupère l'uid de l'utilisateur correspondant dans la session.
* - Si non, on passe quand même un uid spécial signifiant que l'utilisateur n'est pas authentifié.
* Si oui, on récupère l'uid de l'utilisateur correspondant dans la session.
* L'uid qu'on a trouvé est stocké dans req.user, et sera passé à GraphQL, dans le context.
* (Si non, on passera quand même dans context un uid spécial signifiant que l'utilisateur n'est pas authentifié. cf la définition de context, plus loin)
*
* NB : la session ne contient que les correspondances cookie / uid, et rien d'autre.
* passport.serializeUser et .deserializeUser servent en théorie à traduire
*
* https://stackoverflow.com/questions/22052258/what-does-passport-session-middleware-do
*
* On a un petit peu la meme demarche que l'Option 1 de
* https://blog.apollographql.com/a-guide-to-authentication-in-graphql-e002a4039d1
*/
/**
......@@ -98,8 +97,8 @@ app.use(passport.initialize());
// puisqu'elles ne sont pas dans req.session, puisque non-reconnues par app.use(expressSession(...))
//(see http://toon.io/understanding-passportjs-authentication-flow/)
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()}`);
let user = req.user ? req.user.uid : "none";
console.log(`passport.session(): found user: ${user}, authenticated: ${req.isAuthenticated()}`);
next();
});
......@@ -169,7 +168,7 @@ app.post('/login', (req, res, next) => {
*/
// Define GraphQL request's context object, through a callback, with authorization.
// See: https://github.com/apollographql/apollo-server/blob/master/docs/source/best-practices/authentication.md
// See: https://www.apollographql.com/docs/apollo-server/features/authentication.html
const context = async ({ req }) => {
let uid;
......@@ -192,15 +191,17 @@ const context = async ({ req }) => {
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;
// set a special uid for non-authenticated requests
// /!\ FOR DEVELOPMENT ONLY: use the one in the ldap config .json file
// for production, replace with a "publicUser" or "notLoggedInUser" or something.
uid = credentialsLdapConfig.dn.split("=")[1].split(",")[0];
password = credentialsLdapConfig.passwd;
}
return {
request: req,
user: { uid, password }
}
};
}
const server = new ApolloServer({
......@@ -214,7 +215,9 @@ const server = new ApolloServer({
}
});
server.applyMiddleware({ app });
// path defaults to '/graphql'
//server.applyMiddleware({ app, cors: corsOptions }); <-- TODO: is this necessary?
server.applyMiddleware({ app });
/**
* @desc Servir l'interface admin du backend ("adminview")
......@@ -225,6 +228,12 @@ server.applyMiddleware({ app });
* - à une API REST fait-maison (peut-être encore à débugger)
* - à GraphQL Voyager, un package qui permet d'afficher une représentation sous forme de graphe du schéma GraphQL.
*/
// setup the 'pug' view engine. the adminview "sub-app" will inherit this. https://expressjs.com/en/4x/api.html#app.set
//console.log("Express app is running at", __dirname);
let viewpath = path.resolve(__dirname, 'adminview', 'views');
app.set('views', viewpath);
app.set('view engine', 'pug');
app.use('/adminview', router); // catches and resolves HTTP requests to paths '/adminview/*'
/**
......
......@@ -87,4 +87,4 @@ passport.serializeUser(function (user, done) {
passport.deserializeUser(function (userUid, done) {
// console.log(`passport.deserializeUser(): deserializing user ${userUid}`); // DEBUG
done(null, { uid: userUid });
});
\ No newline at end of file
});
/**
* @file Lance le serveur configuré dans {@link server.ts}
* @file Lance le serveur configuré dans {@link app.ts}
* @author manifold
*/
import dotenv from 'dotenv'; //hawkspar->manifold VSCode râle ici pr moi
import dotenv from 'dotenv'; //loads environment variables from (hidden) .env file into process.env
dotenv.config();
import app from './app';
import colors from 'colors';
......
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