isundil 2 年之前
当前提交
b8f7607293
共有 11 个文件被更改,包括 745 次插入0 次删除
  1. 5 0
      .gitignore
  2. 30 0
      main.js
  3. 10 0
      model/DatabaseModel.js
  4. 58 0
      model/session.js
  5. 23 0
      package.json
  6. 15 0
      router/mdi.js
  7. 56 0
      src/config.js
  8. 177 0
      src/databaseHelper.js
  9. 17 0
      src/md5sum.js
  10. 163 0
      src/routerUtils.js
  11. 191 0
      src/security.js

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+/config.json
+/node_modules/
+/package-lock.json
+/memory.sqlite
+

+ 30 - 0
main.js

@@ -0,0 +1,30 @@
+#!/bin/node
+
+const path = require('path');
+const fs = require('fs');
+const Router = require('node-simple-router');
+const http = require('http');
+const CONFIG = require('./src/config.js');
+const Security = require('./src/security.js');
+const RouterUtils = require('./src/routerUtils.js').RouterUtils;
+
+function App() {
+    this.router = new Router({ static_route: __dirname+"/static/" });
+    this.routerUtils = new RouterUtils(this);
+    this.databaseHelper = require('./src/databaseHelper.js').DatabaseHelper;
+}
+
+App.prototype.init = async function() {
+    const _app = this;
+    require('./router/mdi.js').register(this);
+
+    await this.databaseHelper.init();
+}
+
+App.prototype.run = function() {
+    http.createServer(this.router).listen(CONFIG.port);
+}
+
+let app = new App();
+app.init().then(() => app.run());
+

+ 10 - 0
model/DatabaseModel.js

@@ -0,0 +1,10 @@
+
+function DatabaseModel() {
+}
+
+DatabaseModel.prototype.versionColumn = function() { return ""; }
+DatabaseModel.prototype.getTableName = function() { return ""; }
+DatabaseModel.prototype.createOrUpdateBase = function(dbHelper) {}
+
+module.exports.DatabaseModel = DatabaseModel;
+

+ 58 - 0
model/session.js

@@ -0,0 +1,58 @@
+
+const DatabaseModel = require("./DatabaseModel.js").DatabaseModel;
+
+function SessionModel(sessionInfos) {
+    DatabaseModel.call(this);
+    this.userAgent = sessionInfos?.userAgent || "";
+    this.ipAddress = sessionInfos?.ipAddress || "";
+    this.loginDateTime = new Date(sessionInfos?.loginDateTime || 0);
+    this.username = sessionInfos?.username || "";
+    this.mail = sessionInfos?.mail || "";
+    this.sessionId = sessionInfos?.sessionId || "";
+    this.instance = sessionInfos?.instance || "";
+}
+
+SessionModel.prototype = Object.create(DatabaseModel.prototype);
+
+SessionModel.prototype.getTableName = function() {
+    return "session";
+}
+
+SessionModel.prototype.createOrUpdateBase = async function(dbHelper) {
+    await dbHelper.runSql(`CREATE TABLE IF NOT EXISTS 'session' (
+        sessionId STRING NOT NULL,
+        instance string NOT NULL,
+        useragent STRING NOT NULL,
+        remoteAddress STRING NOT NULL,
+        loginDateTime TIMESTAMP NOT NULL,
+        username STRING NOT NULL,
+        mail STRING NOT NULL,
+        PRIMARY KEY (sessionId, instance))`);
+}
+
+SessionModel.prototype.describe = function() {
+    return {
+        "sessionId": this.sessionId,
+        "useragent": this.userAgent,
+        "remoteAddress": this.ipAddress,
+        "loginDateTime": this.loginDateTime.getTime(),
+        "username": this.username,
+        "mail": this.mail,
+        "instance": this.instance
+    };
+}
+
+SessionModel.prototype.versionColumn = function() { return "loginDateTime"; }
+
+SessionModel.prototype.fromDb = function(dbObj) {
+    this.sessionId = dbObj["sessionId"];
+    this.userAgent = dbObj["useragent"];
+    this.ipAddress = dbObj["remoteAddress"];
+    this.loginDateTime.setTime(dbObj["loginDateTime"]);
+    this.username = dbObj["username"];
+    this.mail = dbObj["mail"];
+    this.instance = dbObj["instance"];
+}
+
+module.exports.SessionModel = SessionModel;
+

+ 23 - 0
package.json

@@ -0,0 +1,23 @@
+{
+  "name": "photochamber-webserver",
+  "version": "1.0.0",
+  "description": "Photochamber services",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git:isundil/photochamber-webserver"
+  },
+  "author": "isundil",
+  "license": "ISC",
+  "dependencies": {
+    "crypto": "^1.0.1",
+    "ldap": "^0.7.1",
+    "ldapjs": "^3.0.4",
+    "mime-types": "^2.1.35",
+    "node-simple-router": "^0.10.2",
+    "sqlite3": "^5.1.6"
+  }
+}

+ 15 - 0
router/mdi.js

@@ -0,0 +1,15 @@
+
+module.exports = { register: app => {
+    app.routerUtils.staticGet(app, "/public/mdi/materialdesignicons.css", './node_modules/@mdi/font/css/materialdesignicons.css');
+    app.routerUtils.staticGet(app, "/public/mdi/materialdesignicons.css.map", './node_modules/@mdi/font/css/materialdesignicons.css.map');
+    app.routerUtils.staticGet(app, "/public/mdi/materialdesignicons.min.css", './node_modules/@mdi/font/css/materialdesignicons.min.css');
+    app.routerUtils.staticGet(app, "/public/mdi/materialdesignicons.min.css.map", './node_modules/@mdi/font/css/materialdesignicons.min.css.map');
+    app.routerUtils.staticGet(app, "/public/scss/materialdesignicons.scss", './node_modules/@mdi/font/scss/materialdesignicons.scss');
+    app.routerUtils.staticGet(app, "/public/fonts/materialdesignicons-webfont.eot", './node_modules/@mdi/font/fonts/materialdesignicons-webfont.eot');
+    app.routerUtils.staticGet(app, "/public/fonts/materialdesignicons-webfont.ttf", './node_modules/@mdi/font/fonts/materialdesignicons-webfont.ttf');
+    app.routerUtils.staticGet(app, "/public/fonts/materialdesignicons-webfont.woff", './node_modules/@mdi/font/fonts/materialdesignicons-webfont.woff');
+    app.routerUtils.staticGet(app, "/public/fonts/materialdesignicons-webfont.woff2", './node_modules/@mdi/font/fonts/materialdesignicons-webfont.woff2');
+    app.routerUtils.staticGet(app, "/public/js/tasks.js", './static/public/js/tasks.js');
+    app.routerUtils.staticGet(app, "/favicon.ico", './static/public/img/logo.svg');
+}};
+

+ 56 - 0
src/config.js

@@ -0,0 +1,56 @@
+
+const FILENAME = process.mainModule.path + "/config.json";
+const fs = require('fs');
+const path = require('path');
+
+function validNumber(input) {
+    return !Number.isNaN(Number.parseInt(input));
+}
+
+function validNotEmptyString(input) {
+    return !!input && (""+input).length;
+}
+
+function pickConfig(defaultConfig, configContent) {
+    let configEntries = {};
+    for (let i in defaultConfig) {
+        if (configContent[i] === undefined)
+            configEntries[i] = defaultConfig[i].value;
+        else
+            configEntries[i] = configContent[i];
+        if (!defaultConfig[i].valid(configEntries[i])) {
+            console.error(`Invalid entry in configuration for ${i}: Found ${configEntries[i]}`);
+            hasErrors = true;
+        }
+    }
+    return configEntries;
+}
+
+let hasErrors = false;
+let configEntries = {};
+(() => {
+    let configFile = (process?.argv?.[2] || FILENAME);
+    console.log("CONFIG: Loading configuration from "+configFile);
+    let configContent = JSON.parse(fs.readFileSync(configFile));
+    let defaultConfig = {
+        port: { value: 80, valid: validNumber },
+        instanceHostname: { value: require('os').hostname(), valid: validNotEmptyString },
+        ldapUrl: { value: "", valid: validNotEmptyString },
+        ldapBindDN: { value: "", valid: validNotEmptyString },
+        ldapBindPwd: { value: "", valid: validNotEmptyString },
+        ldapFilter: { value: "", valid: validNotEmptyString },
+        ldapBase: { value: "", valid: validNotEmptyString },
+        database: { value: "", valid: validNotEmptyString },
+        archivePath: { value: path.join(__dirname, "../archives/"), valid: validNotEmptyString },
+        DEBUG_forceLogin: { value: null, valid: () => true }
+    };
+
+    configEntries = pickConfig(defaultConfig, configContent);
+
+    console.log(configEntries);
+    if (hasErrors)
+        throw "Errors found while parsing configuration";
+})();
+
+module.exports = configEntries;
+

+ 177 - 0
src/databaseHelper.js

@@ -0,0 +1,177 @@
+
+const sqlite3 = require('sqlite3');
+const SessionModel = require('../model/session.js').SessionModel;
+const CONFIG = require('./config.js');
+
+let ALL_MODELS = [ SessionModel ];
+
+function DatabaseHelper() {
+    this.db = null;
+}
+
+DatabaseHelper.prototype.init = function() {
+    return new Promise((ok, ko) => {
+        this.db = new sqlite3.Database(CONFIG.database, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE | sqlite3.OPEN_FULLMUTEX, async (err) => {
+            if (err) {
+                ko(err);
+                return;
+            }
+            let types = ALL_MODELS;
+            for (let i =0; i < types.length; ++i) {
+                let instance = new types[i]();
+                await instance.createOrUpdateBase(this);
+            }
+            console.log("Database is ready");
+            ok();
+        });
+    });
+}
+
+DatabaseHelper.prototype.runSql = function(sqlStatement, args) {
+    console.log("DatabaseHelper::runSql ", sqlStatement, args);
+    return new Promise((ok, ko) => {
+        if (sqlStatement.indexOf('--') >= 0) {
+            console.error("Rejecting SQL request containing comments");
+            ko("SQL Comment Error");
+            return;
+        }
+        try {
+            this.db.all(sqlStatement, args, (err, data) => {
+                if (err) {
+                    console.error(err);
+                    ko(err);
+                } else {
+                    ok(data);
+                }
+            });
+        } catch (err) {
+            console.error(err);
+            ko(err);
+        }
+    });
+}
+
+DatabaseHelper.prototype.insertOne = async function(object) {
+    let columns = [];
+    let args = [];
+
+    for (let [key, val] of Object.entries(object.describe())) {
+        columns.push("`"+key+"`");
+        args.push(val);
+    }
+    let request = "insert into `" +object.getTableName()
+            +"` (" +columns.join(", ")
+            +") VALUES (" +columns.map(i => "?").join(",")+")";
+    await this.runSql(request, args);
+}
+
+DatabaseHelper.prototype.upsertOne = async function(obj) {
+    let columns = [];
+    let columnsVal = [];
+    let args = [];
+    let i =0;
+
+    for (let [key, val] of Object.entries(obj.describe())) {
+        columns.push("`"+key+"`");
+        columnsVal.push("`"+key+"`=:"+(i++));
+        args.push(val);
+    }
+    let request = "insert into `" +obj.getTableName()
+            +"` (" +columns.join(", ")
+            +") VALUES (" +columns.map((_, i) => `:${i}`).join(",")+")"
+            +" ON CONFLICT DO UPDATE SET " +columnsVal.join(", ");
+    await this.runSql(request, args);
+}
+
+DatabaseHelper.prototype.buildWhere = function(whereObj, columns, args) {
+    columns = columns || [];
+    args = args || [];
+    if (!whereObj) {
+        columns.push("1=1");
+    } else {
+        for (let [key, val] of Object.entries(whereObj)) {
+            if (val === null) {
+                columns.push("`"+key+"` is null");
+            } else {
+                columns.push("`"+key+"`=?");
+                args.push(val);
+            }
+        }
+    }
+    return {
+        columns: columns,
+        args: args
+    };
+}
+
+DatabaseHelper.prototype.maxVersion = async function(instanceName) {
+    let queryParts = [];
+    for (let i of ALL_MODELS)
+        queryParts.push("select max(" +i.prototype.versionColumn.call(null) +") as v from " +i.prototype.getTableName.call(null) +" where instance=:0");
+    let version = await this.runSql("select max(v) as v from (" +queryParts.join(" union ") +")", [ instanceName ]);
+    return (version?.[0]?.v) || 0;
+}
+
+DatabaseHelper.prototype.versionFetch = async function(objectPrototype, minVersion) {
+    let query = "select * from `" +objectPrototype.prototype.getTableName.call(null) +"` where `" +objectPrototype.prototype.versionColumn.call(null)+"` > ? and instance=?";
+    let result = [];
+    for (let i of await this.runSql(query, [minVersion, CONFIG.instanceHostname]))
+    {
+        let resultObj = new objectPrototype();
+        resultObj.fromDb(i);
+        result.push(resultObj);
+    }
+    return result;
+}
+
+DatabaseHelper.prototype.fetch = async function(objectPrototype, where, orderBy) {
+    let whereArgs = this.buildWhere(where);
+    let query = "select * from `" +objectPrototype.prototype.getTableName.call(null) +"` where " +whereArgs.columns.join(" and ");
+    if (orderBy)
+        query += " ORDER BY " +Object.keys(orderBy || {}).map(i => "`"+i+"` " +(orderBy[i] === 'DESC' ? "DESC":"ASC")).join(",");
+    let result = await this.runSql(query, whereArgs.args);
+    let resultArr = [];
+    for (let i of result)
+    {
+        let resultObj = new objectPrototype();
+        resultObj.fromDb(i);
+        resultArr.push(resultObj);
+    }
+    return resultArr;
+}
+
+DatabaseHelper.prototype.findOne = async function(objectPrototype, where) {
+    let whereArgs = this.buildWhere(where);
+    let result = await this.runSql("select * from `" +objectPrototype.prototype.getTableName.call(null) +"` where " +whereArgs.columns.join(" and "), whereArgs.args);
+    if (result && result.length)
+    {
+        let resultObj = new objectPrototype();
+        resultObj.fromDb(result[0]);
+        return resultObj;
+    }
+    return null;
+}
+
+DatabaseHelper.prototype.update = async function(where, object) {
+    if (object.lastUpdated)
+        object.lastUpdated = new Date();
+    await this.rawUpdate(object, where, object.describe());
+}
+
+DatabaseHelper.prototype.rawUpdate = async function(model, where, values) {
+    let columns = [];
+    let args = [];
+
+    for (let [key, val] of Object.entries(values)) {
+        columns.push("`"+key+"`=?");
+        args.push(val);
+    }
+    let whereArgs = this.buildWhere(where, [], args);
+    let query = "update `" +(model.prototype?.getTableName ? model.prototype.getTableName() : model.getTableName())
+            +"` set " +columns.join(", ")
+            +" WHERE " +whereArgs.columns.join(" and ");
+    await this.runSql(query, args);
+}
+
+module.exports.DatabaseHelper = new DatabaseHelper();
+

+ 17 - 0
src/md5sum.js

@@ -0,0 +1,17 @@
+
+const crypto = require('crypto');
+const fs = require('fs');
+
+function md5File(path) {
+    return crypto.createHash('md5').update(fs.readFileSync(path)).digest('hex');
+}
+
+function md5String(input) {
+    return crypto.createHash('md5').update(input).digest('hex');
+}
+
+module.exports = {
+    file: md5File,
+    string: md5String
+}
+

+ 163 - 0
src/routerUtils.js

@@ -0,0 +1,163 @@
+
+const mime = require('mime-types');
+const path = require('path');
+const fs = require('fs');
+const Security = require('./security.js');
+const CONFIG = require('./config.js');
+
+function RouterUtils(app) {
+    this.app = app;
+}
+
+RouterUtils.prototype.httpResponse = function(res, code, response) {
+    res.writeHead(code);
+    res.end(response);
+    return true;
+}
+
+RouterUtils.prototype.requireLogin =function(req, res) {
+    if (Security.isLoggedUser(req.cookies))
+    {
+        req.loggedUser = Security.getLoggedUser(req.cookies);
+        req.loggedSession = Security.getSessionId(req.cookies);
+        return false;
+    }
+    this.redirect(res, '/login?page='+encodeURIComponent(req.url));
+    return true;
+};
+
+RouterUtils.prototype.apiRequireLogin =function(req, res, validTokens) {
+    if (Security.isLoggedUser(req.cookies))
+    {
+        req.loggedUser = Security.getLoggedUser(req.cookies);
+        req.loggedSession = Security.getSessionId(req.cookies);
+        return false;
+    }
+    if (validTokens && req.body?.apiKey && validTokens.indexOf(req.body?.apiKey) >= 0)
+    {
+        req.loggedUser = req.body.apiKey;
+        req.loggedSession = "";
+        return false;
+    }
+    return this.httpResponse(res, 403, "Unauthorized Access");
+};
+
+RouterUtils.prototype.redirect = function(res, url) {
+    res.writeHead(302, { Location: url });
+    res.end();
+}
+
+RouterUtils.prototype.prepareCookie = function(req) {
+    req.cookies = {};
+    let arr = ((req.headers?.cookie || "").split(';').map(i => i.split('=', 2))).forEach(i => { req.cookies[i[0].trim()] = decodeURIComponent(i[1]).trim();});
+}
+
+RouterUtils.prototype.onRequest = function(req) {
+    this.prepareCookie(req);
+}
+
+RouterUtils.prototype.readPostBody = function(req, res) {
+    const now = Math.floor(Date.now() / 1000);
+    return new Promise((ok, ko) => {
+        if (req.headers['content-type'] !== 'application/json') {
+            console.error("Unexpected input from query: wrong Content-Type");
+            ko();
+            return;
+        }
+        let data = null;
+        try {
+            data = JSON.parse(req.body.data);
+        } catch (e) {
+            console.error("Unexpected input from query: invalid JSON");
+            ko();
+            return;
+        }
+        if (!data.time || Math.abs(now - data.time) > 3) {
+            console.error("Unexpected input from query: Invalid time");
+            ko();
+            return;
+        }
+        if (!data.hostname) {
+            console.error("Unexpected input from query: missing hostname");
+            ko();
+            return;
+        }
+        req.data = data;
+        ok();
+    });
+}
+
+RouterUtils.prototype.apiError = function(res) {
+    res.writeHead(400, { "Content-Type": "application/json"});
+    res.end();
+}
+
+RouterUtils.prototype.jsonResponse = function(res, data) {
+    res.writeHead(200, { "Content-Type": "application/json"});
+    if (typeof data !== 'string')
+        data = JSON.stringify(data);
+    res.end(data);
+}
+
+RouterUtils.prototype.onPageNotFound = function(res) {
+    return this.httpResponse(res, 404, "Page not found...");
+}
+
+RouterUtils.prototype.staticServe = async function(res, filePath) {
+    return new Promise((ok, ko) => {
+        const stream = fs.createReadStream(filePath);
+        const fileSize = fs.statSync(filePath)?.size || undefined;
+        if (!stream || !fileSize) {
+            console.error("RouterUtils::staticGet", filePath, err);
+            this.httpResponse(res, 500, "Internal Server Error");
+            return ko(err);
+        }
+        res.writeHead(200, {
+            "Content-Type": mime.contentType(path.basename(filePath)),
+            "Content-Length": fileSize
+        });
+        stream.pipe(res);
+        stream.once('end', () => ok());
+    });
+}
+
+RouterUtils.prototype.staticGet = function(app, url, staticResources) {
+    app.router.get(url, (req, res) => {
+        app.routerUtils.staticServe(res, staticResources);
+    });
+}
+
+RouterUtils.encodeUrlComponent = function(input) {
+    return btoa(input).replaceAll('=', '-').replaceAll('+', '_');
+}
+
+RouterUtils.prototype.encodeUrlComponent = function(input) {
+    return RouterUtils.encodeUrlComponent(input);
+}
+
+RouterUtils.decodeUrlComponent = function(input) {
+    return atob(input.replaceAll('-', '=').replaceAll('_', '+'));
+}
+
+RouterUtils.prototype.decodeUrlComponent = function(input) {
+    return RouterUtils.decodeUrlComponent(input);
+}
+
+RouterUtils.prototype.commonRenderInfos = function(endpointName) {
+    let context = {
+        page_title: CONFIG.sitename,
+        endpoints: [],
+        currentEndpoint: endpointName
+    };
+    for (let endpoint in CONFIG.endpoints)
+        context.endpoints.push({
+            name: endpoint,
+            address: '/dashboard/'+this.encodeUrlComponent(endpoint),
+            selected: endpoint === endpointName,
+            icon: CONFIG.endpoints[endpoint].icon
+        });
+    return context;
+}
+
+module.exports = { RouterUtils: RouterUtils, encodeUrlComponent: RouterUtils.encodeUrlComponent, decodeUrlComponent: RouterUtils.decodeUrlComponent };
+

+ 191 - 0
src/security.js

@@ -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
+};
+