const fs = require("fs-extra"); const dayjs = require("dayjs"); const glob = require("glob"); // To debug it could be easier with source code: // const openpgp = require("/media/phil/usbfarm/apxtrib/node_modules/openpgp/dist/node/openpgp.js"); const openpgp = require("openpgp"); /** * Check authentification and get person profils for a tribe * @param {object} req * @param {object} res * @param {function} next * @returns {status:} * * 3 steps: * - clean eventual tokens oldest than 24 hours (the first pagan that authenticate of the day will process this) * - if token present in /town/tmp/tokens/alias_tribe_part of the xhash return xprofils with list of profils pagans * - if no token then check xhash with openpgp lib and create one * * All data related are store in town/tmp/tokens backend, and localstorage headers for front end * A penalty function increase a sleep function between 2 fail try of authentification to avoid bruteforce */ const isAuthenticated = async (req, res, next) => { // tokens if valid are store in /dirtown/tmp/tokens/xalias_xdays_xhash(20,200) // once a day rm oldest tokens than 24hours tag job by adding tmp/tokensmenagedone{day} const withlog = true; const currentday = dayjs().date(); fs.ensureDirSync(`${process.env.dirtown}/tmp/tokens`); let menagedone = fs.existsSync( `${process.env.dirtown}/tmp/tokens/menagedone${currentday}` ); if (withlog) console.log(`menagedone${currentday} was it done today?:${menagedone}`); if (!menagedone) { // clean oldest const tsday = dayjs().valueOf(); // now in timestamp format glob.sync(`${process.env.dirtown}/tmp/tokens/menagedone*`).forEach((f) => { fs.removeSync(f); }); glob.sync(`${process.env.dirtown}/tmp/tokens/*.json`).forEach((f) => { const fsplit = f.split("_"); const elapse = tsday - parseInt(fsplit[2]); //24h 86400000 milliseconde 15mn 900000 if (elapse && elapse > 86400000) { fs.remove(f); } }); fs.outputFile( `${process.env.dirtown}/tmp/tokens/menagedone${currentday}`, "done by middleware/isAUthenticated" ); } //Check register in tmp/tokens/ if (withlog) console.log("isAuthenticate?", req.session.header, req.body); const resnotauth = { ref: "middlewares", msg: "notauthenticated", data: { xalias: req.session.header.xalias, xaliasexists: true, }, }; if ( req.session.header.xalias == "anonymous" || req.session.header.xhash == "anonymous" ) { if (withlog) console.log("alias anonymous means not auth"); resnotauth.status = 401; return res.status(resnotauth.status).json(resnotauth); } let tmpfs = `${process.env.dirtown}/tmp/tokens/${req.session.header.xalias}_${req.session.header.xtribe}_${req.session.header.xdays}`; //max filename in ext4: 255 characters tmpfs += `_${req.session.header.xhash.substring( 150, 150 + tmpfs.length - 249 )}.json`; /** * * @param {string} alias that request an access * @param {string} action "clean" | "penalty" */ const bruteforcepenalty = async (alias, action) => { const sleep = (ms) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; const failstamp = `${process.env.dirtown}/tmp/tokens/${alias}.json`; if (action == "clean") { //to reinit bruteforce checker if (withlog) console.log("try to clean penalty file ", failstamp); fs.remove(failstamp, (err) => { if (err) console.log("Check forcebrut ", err); }); } else if (action == "penalty") { const stamp = fs.existsSync(failstamp) ? fs.readJSONSync(failstamp) : { lastfail: dayjs().format(), numberfail: 0 }; stamp.lastfail = dayjs().format(); stamp.numberfail += 1; fs.outputJSON(failstamp, stamp); if (withlog) console.log("penalty:", stamp); await sleep(stamp.numberfail * 100); //increase of 0,1 second the answer time per fail if (withlog) console.log("time out penalty"); } }; if (!fs.existsSync(tmpfs)) { // need to check detached sign let publickey = ""; console.log(process.cwd()); console.log(process.env.PWD); console.log(__dirname); const aliasinfo = `${process.env.PWD}/nationchains/pagans/itm/${req.session.header.xalias}.json`; if (fs.existsSync(aliasinfo)) { publickey = fs.readJsonSync(aliasinfo).publickey; } else if (req.body.publickey) { resnotauth.data.xaliasexists = false; publickey = req.body.publickey; } if (publickey == "") { if (withlog) console.log("alias unknown"); resnotauth.status = 404; resnotauth.data.xaliasexists = false; return res.status(resnotauth.status).send(resnotauth); } if (withlog) console.log("publickey", publickey); if (publickey.substring(0, 31) !== "-----BEGIN PGP PUBLIC KEY BLOCK") { if (withlog) console.log("Publickey is not valid as armored key:", publickey); await bruteforcepenalty(req.session.header.xalias, "penalty"); resnotauth.status = 404; return res.status(resnotauth.status).send(resnotauth); } const clearmsg = Buffer.from(req.session.header.xhash, "base64").toString(); if (clearmsg.substring(0, 10) !== "-----BEGIN") { if (withlog) console.log("xhash conv is not valid as armored key:", clearmsg); await bruteforcepenalty(req.session.header.xalias, "penalty"); resnotauth.status = 404; return res.status(resnotauth.status).send(resnotauth); } if (withlog) console.log("clearmsg", clearmsg); const pubkey = await openpgp.readKey({ armoredKey: publickey }); const signedMessage = await openpgp.readCleartextMessage({ cleartextMessage: clearmsg, }); const verificationResult = await openpgp.verify({ message: signedMessage, verificationKeys: pubkey, }); if (withlog) console.log(verificationResult); if (withlog) console.log(verificationResult.signatures[0].keyID.toHex()); try { await verificationResult.signatures[0].verified; if ( verificationResult.data != `${req.session.header.xalias}_${req.session.header.xdays}` ) { resnotauth.msg = "signaturefailled"; if (withlog) console.log( `message recu:${verificationResult.data} , message attendu:${req.session.header.xalias}_${req.session.header.xdays}` ); await bruteforcepenalty(req.session.header.xalias, "penalty"); resnotauth.status = 401; return res.status(resnotauth.status).send(resnotauth); } } catch (e) { resnotauth.msg = "signaturefailled"; if (withlog) console.log("erreur", e); await bruteforcepenalty(req.session.header.xalias, "penalty"); resnotauth.status = 401; return res.status(resnotauth.status).send(resnotauth); } // authenticated then get person profils (person = pagan for a xtrib) req.session.header.xprofils.push("pagans"); const person = `${process.env.dirtown}/tribes/${req.session.header.xtribe}/persons/itm/${req.session.header.xalias}.json`; if (withlog) { console.log("Profils tribe/app management"); console.log("person", person); } if (fs.existsSync(person)) { const infoperson = fs.readJSONSync(person); console.log(infoperson); infoperson.profils.forEach((p) => req.session.header.xprofils.push(p)); } fs.outputJSONSync(tmpfs, req.session.header.xprofils); } else { //tmpfs exist get profils from identification process req.session.header.xprofils = fs.readJSONSync(tmpfs); } bruteforcepenalty(req.session.header.xalias, "clean"); console.log(`${req.session.header.xalias} Authenticated`); next(); }; module.exports = isAuthenticated;