|
|
@@ -0,0 +1,191 @@
|
|
|
+
|
|
|
+const CONFIG = require('./config.js');
|
|
|
+const SESSION_TIME = 2 * 1 * 60 * 60 * 1000; // 2h
|
|
|
+const SESSION_COOKIE = "_sessionId";
|
|
|
+const crypto = require('crypto');
|
|
|
+const ldapjs = require('ldapjs');
|
|
|
+const ldap = ldapjs.createClient({
|
|
|
+ url: [ CONFIG.ldapUrl, CONFIG.ldapUrl ],
|
|
|
+ reconnect: true
|
|
|
+ });
|
|
|
+const MD5 = require('./md5sum.js').string;
|
|
|
+const SessionModel = require('../model/session.js').SessionModel;
|
|
|
+
|
|
|
+let loggedCache = {};
|
|
|
+
|
|
|
+let ldapReady = new Promise((ok, ko) => {
|
|
|
+ ldap.on("error", (err) => { console.error("LDAP Error: " +err) });
|
|
|
+ ldap.bind(CONFIG.ldapBindDN, CONFIG.ldapBindPwd, (err) => {
|
|
|
+ if (err) {
|
|
|
+ console.error(err);
|
|
|
+ ko(err);
|
|
|
+ throw err;
|
|
|
+ }
|
|
|
+ console.log("LDAP is ready");
|
|
|
+ ok();
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+function getSessionId(cookieObject) {
|
|
|
+ return cookieObject?.[SESSION_COOKIE];
|
|
|
+}
|
|
|
+
|
|
|
+function getLoggedUser(cookieObject) {
|
|
|
+ let cookie = getSessionId(cookieObject);
|
|
|
+ if (!cookie)
|
|
|
+ return null;
|
|
|
+ let sessionEntry = loggedCache[cookie];
|
|
|
+ const now = (new Date()).getTime();
|
|
|
+ if (!sessionEntry || sessionEntry.expire < now)
|
|
|
+ return null;
|
|
|
+ sessionEntry.expire = now + SESSION_TIME;
|
|
|
+ return sessionEntry.username;
|
|
|
+}
|
|
|
+
|
|
|
+function isUserValid(username) {
|
|
|
+ return new Promise((ok, ko) => {
|
|
|
+ ldapReady.then(() => {
|
|
|
+ ldap.search(CONFIG.ldapBase, { filter: CONFIG.ldapFilter?.replaceAll('{0}', username), attributes: [ 'mail', 'cn', 'sn', 'dn' ], scope: 'one' }, (err, res) => {
|
|
|
+ if (err) {
|
|
|
+ console.error(err);
|
|
|
+ ko(err);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let found = null;
|
|
|
+ res.on('searchEntry', i => found = i.object);
|
|
|
+ res.on('error', console.error);
|
|
|
+ res.on('end', () => {
|
|
|
+ if (!found) {
|
|
|
+ ko();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const now = (new Date()).getTime();
|
|
|
+ let sessionEntry = {
|
|
|
+ loginDateTime: now,
|
|
|
+ expire: now + SESSION_TIME,
|
|
|
+ username: found.cn || found.sn,
|
|
|
+ mail: Array.isArray(found.mail) ? found.mail[0] : found.mail,
|
|
|
+ dn: found.dn,
|
|
|
+ random: Math.random()
|
|
|
+ };
|
|
|
+ ok(sessionEntry);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function checkPassword(dn, password) {
|
|
|
+ return new Promise((ok, ko) => {
|
|
|
+ let ldap = require('ldapjs').createClient({ url: [ CONFIG.ldapUrl, CONFIG.ldapUrl ]});
|
|
|
+ ldap.on('error', err => {
|
|
|
+ console.error("Login failure: " +err);
|
|
|
+ ko(err.message);
|
|
|
+ });
|
|
|
+ ldap.bind(dn, password, err => {
|
|
|
+ if (err) {
|
|
|
+ console.error("Login failure: " +err);
|
|
|
+ ko(err instanceof ldapjs.InvalidCredentialsError ? undefined : err.message);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ldap.unbind();
|
|
|
+ ok();
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function getRequestIp(req) {
|
|
|
+ return req.headers['x-forwarded-for'] || req.socket.remoteAddress;
|
|
|
+}
|
|
|
+
|
|
|
+function sign(msg) {
|
|
|
+ return crypto.sign('sha256', Buffer.from(msg), decodeKey(CONFIG.privKey)).toString('base64');
|
|
|
+}
|
|
|
+
|
|
|
+function checkSign(remoteHostConfig, message, signature) {
|
|
|
+ let result = false;
|
|
|
+ try {
|
|
|
+ result = crypto.verify('sha256', Buffer.from(message), decodeKey(remoteHostConfig.pubKey), Buffer.from(signature, 'base64'));
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e);
|
|
|
+ result = false;
|
|
|
+ }
|
|
|
+ if (!result)
|
|
|
+ console.error("Crypto::checkSign failed to check signature for message !");
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+function encodeKey(key) { return btoa(key); }
|
|
|
+function decodeKey(key) { return atob(key); }
|
|
|
+
|
|
|
+function generateKeys() {
|
|
|
+ return new Promise((ok, ko) => {
|
|
|
+ crypto.generateKeyPair('rsa', {
|
|
|
+ modulusLength: 4096,
|
|
|
+ publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
|
+ privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
|
|
|
+ }, (err, pubKey, privKey) => {
|
|
|
+ if (err) {
|
|
|
+ ko(err);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ ok({pubKey: encodeKey(pubKey), privKey: encodeKey(privKey)});
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = {
|
|
|
+ isLoggedUser: (cookieObject) => {
|
|
|
+ return getLoggedUser(cookieObject) !== null;
|
|
|
+ },
|
|
|
+ getLoggedUser: getLoggedUser,
|
|
|
+ getRequestIp: getRequestIp,
|
|
|
+ getSessionId: (cookieObj) => { return getLoggedUser(cookieObj) ? getSessionId(cookieObj) : null },
|
|
|
+ forceLogin: (app, req) => {
|
|
|
+ if (!CONFIG.DEBUG_forceLogin)
|
|
|
+ throw "Security::ForceLogin: Access Denied";
|
|
|
+ const wrongCredentialsMsg = "Invalid username or password";
|
|
|
+ return new Promise((ok, ko) => {
|
|
|
+ isUserValid(CONFIG.DEBUG_forceLogin).catch(err => {
|
|
|
+ ko(err ? ("Internal error: " +err) : wrongCredentialsMsg);
|
|
|
+ }).then(sessionInfos => {
|
|
|
+ sessionInfos.userAgent = req.headers['user-agent'];
|
|
|
+ sessionInfos.instance = CONFIG.instanceHostname;
|
|
|
+ sessionInfos.ipAddress = getRequestIp(req);
|
|
|
+ let sessionKey = MD5(JSON.stringify(sessionInfos));
|
|
|
+ sessionInfos.sessionId = sessionKey;
|
|
|
+ loggedCache[sessionKey] = sessionInfos;
|
|
|
+ let sessionDbModel = new SessionModel(sessionInfos);
|
|
|
+ app.databaseHelper.insertOne(new SessionModel(sessionInfos));
|
|
|
+ ok(sessionKey);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ tryLogin: (app, req, username, password) => {
|
|
|
+ const wrongCredentialsMsg = "Invalid username or password";
|
|
|
+ return new Promise((ok, ko) => {
|
|
|
+ isUserValid(username).catch(err => {
|
|
|
+ ko(err ? ("Internal error: " +err) : wrongCredentialsMsg);
|
|
|
+ }).then(sessionInfos => {
|
|
|
+ return checkPassword(sessionInfos.dn, password).then(() => {
|
|
|
+ sessionInfos.userAgent = req.headers['user-agent'];
|
|
|
+ sessionInfos.instance = CONFIG.instanceHostname;
|
|
|
+ sessionInfos.ipAddress = getRequestIp(req);
|
|
|
+ let sessionKey = MD5(JSON.stringify(sessionInfos));
|
|
|
+ sessionInfos.sessionId = sessionKey;
|
|
|
+ loggedCache[sessionKey] = sessionInfos;
|
|
|
+ let sessionDbModel = new SessionModel(sessionInfos);
|
|
|
+ app.databaseHelper.insertOne(new SessionModel(sessionInfos));
|
|
|
+ ok(sessionKey);
|
|
|
+ });
|
|
|
+ }).catch(err => {
|
|
|
+ ko(err ? ("Internal error: " +err) : wrongCredentialsMsg);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ sign: sign,
|
|
|
+ checkSign: checkSign,
|
|
|
+ generateKeys: generateKeys,
|
|
|
+ SESSION_COOKIE: SESSION_COOKIE
|
|
|
+};
|
|
|
+
|