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"); /** * @api{get}/istauthenticated * @apiGroup Middlewares * @apiName isAUthenticated * @apiDescription Check that exist in town/tmp/tokens/xalias_xdays_xhash.substr(20,200) if not, check the xhash signature with message xalias_xdays come from public key belonging to xalias. If check pass then store a xhash into /tmp/tokens. * A process run each day to clean up all xhas tmp/tokens oldest than 24 hours. * If authentify it returns header with xprofils store into a person objject -xtribes/person/alias * * @apiHeader {string} xalias anonymous or unique alias * @apiHeader {string} xapp name of the webapp store in tribe/tribeid/www/xapp * @apiHeader {string} xlang the 2 letter request langage (if does not exist then return en = english). * @apiHeader {string} xtribe unique tribe name ere xapp exist * @apiHeader {string} xdays a timestamp 0 or generate during the authentifyme process * @apiHeader {string} xhash anonymous or signature of message: xalias_xdays created by alias private key during authentifyme process * @apiHeader {array[]} xprofils list of string profil apply into xtribe for xapp * @apiHeader {string} xuuid a unique number c reated the fisrt time a domain is visited * @apiHeader {integer} xtrkversion a version number link to tracking system * * @apiErrorExample {json} Error-Response: * HTTP/1/1 400 Not Found * { * status:400, * ref:"middlewares" * msg:"missingheaders", * data: ["headermissing1"] * } *@apiErrorExample {json} Error-Response: * HTTP/1/1 404 Not Found * { * status:404, * ref:"middlewares" * msg:"tribeiddoesnotexist", * data: {xalias} * } * * @apiHeaderExample {json} Header-Exemple: * { * xtribe:"apache", * xalias:"toto", * xhash:"", * xdays:"123" * xlang:"en", * xapp:"popular" * } **/ const isAuthenticated = async (req, res, next) => { 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`; 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;