diff --git a/backend/service-authentication/auth.js b/backend/service-authentication/auth.js new file mode 100644 index 0000000..a5a5487 --- /dev/null +++ b/backend/service-authentication/auth.js @@ -0,0 +1,44 @@ +const sessionJwt = require ('./sessionJWT'); +const queries = require('./mongodbQueries'); + +// ici, on récupère le contenu du cookie de session JWT. +// celui-ci contient le userId mais également des informations +// concernant sa date d'expiration. +function getSession (req) { + return sessionJwt.decodeSessionCookie(req); +} +module.exports.getSession = getSession; + +// cette fonction ajoute le cookie de session au headers du +// message qui sera renvoyé à Angular. Si le cookie actuel +// est "vieux", on en recrée ici un nouveau. +function setSessionCookie (req, res, session) { + sessionJwt.createSessionCookie(req, res, session); +} +module.exports.setSessionCookie = setSessionCookie; + +// fonction pour récupérer le userId provenant du cookie +// de session. Si ce dernier n'existe pas, on renvoie +// l'ID -1. +function getUserId(session) { + if (typeof session.userId === 'undefined') return -1; + return session.userId; +} +module.exports.getUserId = getUserId; + +async function authenticate(req, res) { + const login = req.body.login; + const password = req.body.password; + + const userList = await queries.checkLoginQuery(login, password); + if (userList !== undefined && userList[0] !== undefined && userList[0].idUtilisateur > 0){ + setSessionCookie (req, res, { userId: userList[0].idUtilisateur }); + // return userList[0].idUtilisateur; + return getUserId(getSession(req)); + } else { + setSessionCookie (req, res, {userId: -1}); + return -1; + } + +} +module.exports.authenticate = authenticate; diff --git a/backend/service-authentication/checkLogin.js b/backend/service-authentication/checkLogin.js new file mode 100644 index 0000000..e8a1dce --- /dev/null +++ b/backend/service-authentication/checkLogin.js @@ -0,0 +1,18 @@ +const {sendError, sendMessage} = require ("./message"); +const auth = require ('./auth'); + +async function checkLogin (req,res) { + if (typeof req.body.login === 'undefined') + return sendError(res, 'Vous n\'avez pas envoyé le champ login'); + if (typeof req.body.password === 'undefined') + return sendError(res, 'Vous n\'avez pas envoyé le champ password'); + + const result = await auth.authenticate(req, res); + if (result){ + return sendMessage(res, result); + } + else{ + return sendError(res, 'Invalid username or password'); + } +} +module.exports = checkLogin; diff --git a/backend/service-authentication/config.js b/backend/service-authentication/config.js new file mode 100644 index 0000000..ad36165 --- /dev/null +++ b/backend/service-authentication/config.js @@ -0,0 +1,15 @@ +const config = { + // paramètres de connexion à la base de données + mongodbDatabase: 'chat', + mongodbHost: 'mongodb://localhost:27017/', + charset: 'utf8', + mongodbLogin: '', + mongodbPassword: '', + + // les noms des tables + mongodbUtilisateurs: 'users' +}; +module.exports = config; + + + diff --git a/backend/service-authentication/keys/jwtRS256.key b/backend/service-authentication/keys/jwtRS256.key new file mode 100644 index 0000000..d8fc20c --- /dev/null +++ b/backend/service-authentication/keys/jwtRS256.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAvtgwBVQ26VNO1XD+PTZDdZJdWXufontRZIO/ev7OkvH8qM6g +mZDJOZQRrbaMI2w3jGr/x1uDhIaafRtyRsQ9Jdjb2GVjQc9deiNMfeEvA1YJZjiC +LDy4igtmitPup3WrG+xb9asNAuQUtn1bXKM/3ecVxIKq6j/wh1578ra9WGpcTZjx ++1u+3LjviWehb9Gwu8FOIPBu9bcIf48JZLr3Zq/0Fv0Atw7gTJBDAb2B9Zsjxc59 +oKKyWguRQG6COUaxZ/Lsyczv0FIFFsfAKzuihqfI+EsVGJ1nsFBH3gAdeUW2RX7/ +z+7cmR305sf3cdN7kcK8ZwgrG7/lnszSUf79uQIDAQABAoIBAAKKDdmETVa9bQca +Acf+uNcPo35VTsJysZqMxjd0vHroDVyRo0Fn08WYWsPc8fBmuDZ+FfkpFo7k25le +jB607btbL1Gpx7BQegXuAk6KeH2q32Uky1abWDk9/ZdLSpmfzy8H7hFOvVWUg1IK +SPTqlyb0tC1xL6T+RM0ITM3l8I8ElWHKzhs5H9jLlhOng0sW9RbLjWtBRXPp48tB +YFhx/wuSNpTOVGZAKpWVMaCgOizeL0yT6t+z/QL7Pvq3IhlcFMLz5K6f6/mZ+ODV +W/TpbwC7ll204vmgHsYY6tdpDAKqZK0ayPxm018QCCtCsYawDyk2HjYYnytXSsNu +UNPt+KECgYEA6WkKd5Nh8K0m9LVL3Gpbep9MKTqGgBVo8z4UV6iSDQdCBUSU8cFi +2ntsyAh0PCvQHxvn/DwqCcKafbFQMX+S1jpkZsoqA0OUjYRxQYOEn1iIGJdspzYl +cv2637p3Aza6x75eFLOZvFa3C1rCFQxJHvAYolN/FnhBx9D3QnaDydsCgYEA0VCJ +z4kVFm35poBEAVJOO3fdmFUvAn3bFhgI5zfzC/e4dPs9beiWo9XmpeOapu9D7F3M +1OFITUw1yQBoDtG5Gt/cIp39wdPNLfDM3kgv/djQGa/EZO1NkDrk/AGDZqyzDTUV +vGB/d8dGQdP9buiZFcy9D3HSsNVD9RP547+mfPsCgYEAywfa2f6yr/b+LthQ368I +WdadGjPVcS6udv+mFEYGnRyWGhz47n4IbgH0st9ftENsKtNMbQUskAbd+b22Awh0 +grKSSTNLbkFnw93T4mfzgeQxpip5kc2wr6Dz387D3WDLAhqenaVIciJ/4HmW31Yr +eKTi2LiGkNYUaipkGkUbCAcCgYEAmHvCmWT07s0ZJLmmUQwjn+D3lqTfxUHoW9UQ +j1jL/3jscJQisTHSo/IMv+bqYBhH9CZ1NNQVdvJA8HgSVlFkNvbECJfuJ8jjXUdi +B8Cw7Y2INF4+hYP2kE7HR+rWTfMb0VBN0FjAI4MRZJ1JsAVUmHP5ewnhgh8rDZwK +3GZepAcCgYEA1LwjdqCg3LJzXVSJ6gWwNNB9gRX2bb2l7iMHxaTEyRFvJFW7cuL9 +Y7iCKw+9EpGqSZesIHk1V+TCj77nXKpsyO+/tmiyyeqa1DT+AvvG+IgB0aJto7kU +f2oxn02dX9hN51tRhHZ6PDV/43GnJ3ltaXLBhCxq/MQZFXztYkUd6Z0= +-----END RSA PRIVATE KEY----- diff --git a/backend/service-authentication/keys/jwtRS256.key.pub b/backend/service-authentication/keys/jwtRS256.key.pub new file mode 100644 index 0000000..e316812 --- /dev/null +++ b/backend/service-authentication/keys/jwtRS256.key.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvtgwBVQ26VNO1XD+PTZD +dZJdWXufontRZIO/ev7OkvH8qM6gmZDJOZQRrbaMI2w3jGr/x1uDhIaafRtyRsQ9 +Jdjb2GVjQc9deiNMfeEvA1YJZjiCLDy4igtmitPup3WrG+xb9asNAuQUtn1bXKM/ +3ecVxIKq6j/wh1578ra9WGpcTZjx+1u+3LjviWehb9Gwu8FOIPBu9bcIf48JZLr3 +Zq/0Fv0Atw7gTJBDAb2B9Zsjxc59oKKyWguRQG6COUaxZ/Lsyczv0FIFFsfAKzui +hqfI+EsVGJ1nsFBH3gAdeUW2RX7/z+7cmR305sf3cdN7kcK8ZwgrG7/lnszSUf79 +uQIDAQAB +-----END PUBLIC KEY----- diff --git a/backend/service-authentication/keys/jwtRS256.sh b/backend/service-authentication/keys/jwtRS256.sh new file mode 100644 index 0000000..0fe22a2 --- /dev/null +++ b/backend/service-authentication/keys/jwtRS256.sh @@ -0,0 +1,5 @@ +ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key +# Don't add passphrase +openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub +cat jwtRS256.key +cat jwtRS256.key.pub \ No newline at end of file diff --git a/backend/service-authentication/message.js b/backend/service-authentication/message.js new file mode 100644 index 0000000..05714bc --- /dev/null +++ b/backend/service-authentication/message.js @@ -0,0 +1,13 @@ +// renvoie un message au format JSON. On a besoin de passer en paramètre +// res, la réponse que l'on envoie au client (Angular). Le paramètre +// data est un objet JavaScript. Globalement, cette fonction est +// équivalente au "echo json_encode(data);" que vous utilisiez en PHP +function sendMessage (res, data) { + res.json ({ status: 'ok', data: data }); +} + +function sendError (res, reason) { + res.json ({ status: 'error', data: {reason: reason }}); +} + +module.exports = { sendMessage, sendError }; diff --git a/backend/service-authentication/mongodbConnect.js b/backend/service-authentication/mongodbConnect.js new file mode 100644 index 0000000..004ee4e --- /dev/null +++ b/backend/service-authentication/mongodbConnect.js @@ -0,0 +1,18 @@ +const config = require('./config'); +const MongoClient = require( 'mongodb' ).MongoClient; +const uri = config.mongodbHost; +let db; + +module.exports = { + connectToServer: function( callback ) { + MongoClient.connect( uri, { useNewUrlParser: true, useUnifiedTopology: true }, function( err, client ) { + db = client.db(config.mongodbDatabase); + return callback( err ); + }); + }, + + getDB: function() { + return db; + } +}; + diff --git a/backend/service-authentication/mongodbQueries.js b/backend/service-authentication/mongodbQueries.js new file mode 100644 index 0000000..62bb37a --- /dev/null +++ b/backend/service-authentication/mongodbQueries.js @@ -0,0 +1,16 @@ +const config = require('./config'); +const mongoDB = require ('./mongodbConnect').getDB(); + +function checkLoginQuery(login, password){ + // SELECT idUtilisateurs + // FROM utilisateurs + // WHERE login = ? AND password = ?; + return new Promise((resolve, reject) => { + resolve(mongoDB.collection(config.mongodbUtilisateurs).find( + {login: login, password: password}, + {projection: {idUtilisateur: 1, _id: 0}}).toArray()); + }); +} +module.exports.checkLoginQuery = checkLoginQuery; + + diff --git a/backend/service-authentication/package.json b/backend/service-authentication/package.json new file mode 100644 index 0000000..c98b345 --- /dev/null +++ b/backend/service-authentication/package.json @@ -0,0 +1,19 @@ +{ + "name": "service-authentication", + "version": "1.0.0", + "description": "", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Van Khai PHAN", + "license": "ISC", + "dependencies": { + "body-parser": "^1.19.0", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "mongodb": "^3.6.9" + } +} diff --git a/backend/service-authentication/server.js b/backend/service-authentication/server.js new file mode 100644 index 0000000..b5eb59d --- /dev/null +++ b/backend/service-authentication/server.js @@ -0,0 +1,32 @@ +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; + +const cookieParser = require('cookie-parser'); +app.use(cookieParser()); + +const bodyParser = require('body-parser'); +app.use(bodyParser.urlencoded({extended:true})); +app.use(bodyParser.json()); + +const cors = require('cors'); +app.use(cors({origin: 'http://127.0.0.1:4200', credentials: true})); + +const mongoConnect = require('./mongodbConnect'); + +mongoConnect.connectToServer(function( err, client ) { + if (err) + console.log(err); + + const checkLogin = require('./checkLogin'); + + app.post('/checkLogin', (req, res) => { + checkLogin(req,res); + }); + + app.listen(port, () => { + console.log (`listening on port ${port}`); + }); +}); + + diff --git a/backend/service-authentication/sessionJWT.js b/backend/service-authentication/sessionJWT.js new file mode 100644 index 0000000..90133d1 --- /dev/null +++ b/backend/service-authentication/sessionJWT.js @@ -0,0 +1,88 @@ +const sessionJWT = require ('jsonwebtoken'); +const fs = require ('fs'); + +// renvoie un nouveau token JWT +function createSessionJWT (userId) { + // ci-dessous, on met en place le cookie de session JWT : + // 1/ on recupere notre clef privee + const RSA_PRIVATE_KEY = fs.readFileSync('./keys/jwtRS256.key'); + + // 2/ on signe un token JWT. Le payload est l'identifiant de + // l'utilisateur ainsi qu'une date d'expiration à mi-parcours : + // on récupérra ultérieurement ces informations, qui permettront + // de savoir si le token est valide ou non et de connaître l'ID + // de l'utilisateur. Dans le token, le champ exp indique la date + // de validité du token (pas besoin de se relogguer tant que la + // date actuelle est inférieure à exp) et le champ midExp indique + // à partir de quel moment on doit recréer un nouveau cookie de + // session. + const jwtToken = sessionJWT.sign( + { + userId: userId, + midExp: Math.floor(Date.now() / 1000) + 1800 // validité: 30mn + }, + RSA_PRIVATE_KEY, + { + algorithm: 'RS256', + expiresIn: '1h' // champ exp: validité 1h + }); + + return jwtToken; +} + + +// crée un cookie de session JWT (Si le JWT de la requête est encore valide, +// on l'utilise, sinon on en recrée un nouveau) +function createSessionCookie(req, res, payload) { + // on regarde si le payload contient les champs userId et midExp. Si c'est le + // cas, c'est qu'on a reçu dans la request un cookie. On va donc vérifier si + // ce cookie est encore valide ou non : si la date actuelle est inférieure à + // midExp, alors le cookie est encore valide et on peut le renvoyer. Sinon, + // on doit recalculer un nouveau cookie. + let jwtToken = ''; + if ((typeof payload.userId !== 'undefined') && + (typeof payload.midExp !== 'undefined') && + (Math.floor(Date.now() / 1000) <= payload.midExp)) { + jwtToken = req.cookies.SESSIONID; + } + else { + // on crée un nouveau cookie + jwtToken = createSessionJWT(payload.userId); + } + + // on renvoie le cookie au client + // on met le secure à false afin de pouvoir utiliser http plutôt que https + res.cookie('SESSIONID', jwtToken, {httpOnly:true, secure:false}); +} +module.exports.createSessionCookie = createSessionCookie; + + +// décode un cookie de session et renvoie les informations contenues dans ce +// cookie, notamment le userId. Si le cookie n'existe pas, la fonction renvoie +// juste un objet avec un userId égal à -1. +function decodeSessionCookie(req) { + // si l'on n'a pas de cookie de session, on renvoie une session avec vide, + // avec juste un userId à -1 + console.log(req.cookies); + if (typeof req.cookies.SESSIONID === 'undefined') { + return { userId: -1 }; + } + const sessionid = req.cookies.SESSIONID; + + // on lit la clef publique + const RSA_PUBLIC_KEY = fs.readFileSync('./keys/jwtRS256.key.pub'); + + // on récupère les données du cookie + try { + const token = sessionJWT.verify( + sessionid, + RSA_PUBLIC_KEY, + {algorithms: ['RS256']}); + return token; + } + catch (err) { + return {userId: -1}; + } +} +module.exports.decodeSessionCookie = decodeSessionCookie; +