const fs = require("fs-extra"); const dayjs = require("dayjs"); //const path=require('path'); const glob = require("glob"); // To debug it could be easier with source code: // const openpgp = require("/media/phil/usbfarm/apxtri/node_modules/openpgp/dist/node/openpgp.js"); const openpgp = require("openpgp"); const conf = require(`../../../conf.json`); const currentmod='isAuthenticated'; const log = conf.api.activelog.includes(currentmod) /** * @api {get} / - isAuthenticated * @apiGroup Middlewares * @apiName isAuthenticated * @apiDescription - valid if exist xalias_xdays_xhash.substr(20,200) in town/tmp/tokens/ * - if not, * - valid if xhash signature sign xalias_xdays with alias's publickey. * - if not valid => not allowed * - If valid => * - store a xalias_xdays_xhash.substr (20,200) into /tmp/tokens with xprofils array from person. * - update header.xprofils from this token * * apxtri profils are anonymous, pagans, mayor (on a node server), druid (on a tribe like smatchit). * * pagan identity is independant of domain (tribe), by default profils are :['anonymous','pagans']. if this alias exist in a tribe domain as a person then his profils come from /tribes/{tribeId}/objects/person/itm/{alias}.json profils:['anonymous','pagans','person','seeker'] any profils allowed to act on tribe objects. * * Each profil have CRUD accessright on object managed in schema in apxaccessrights:{owner,profil:{"C":[],"R":[properties],"U":[properties],"D":[]}}, see Odmdb for details. * * A process run once each day to clean up all xhash tmp/tokens oldest than 24 hours. * **/ const isAuthenticated = async (req, res, next) => { /* console.log(__dirname) console.log(path.resolve('../tmp/tokens')) if (fs.existsSync('../tmp/tokens')) console.log('pass A') if (fs.existsSync('../tmp/tokens')) console.log('pass B') */ const currentday = dayjs().date(); fs.ensureDirSync(`../tmp/tokens`); let menagedone = fs.existsSync( `../../tmp/tokens/menagedone${currentday}` ); if (menagedone) console.log(Date.now(),`menagedone${currentday} was it done today?:${menagedone}`); if (!menagedone) { // clean oldest const tsday = dayjs().valueOf(); // now in timestamp format glob.sync(`../../tmp/tokens/menagedone*`).forEach((f) => { fs.removeSync(f); }); glob.sync(`../../tmp/tokens/*.json`).forEach((f) => { const fsplit = f.split("_"); const elapse = tsday - parseInt(fsplit[2]); console.log("##############################") console.log(fsplit,"--",fsplit[2]) //24h 86400000 milliseconde 15mn 900000 if (elapse && elapse > 86400000) { fs.remove(f); } }); //clean tmp glob.sync(`../../tmp/*.txt`).forEach((f) => { fs.remove(f); }); fs.outputFile( `../../tmp/tokens/menagedone${currentday}`, "done by middleware/isAUthenticated" ); } //Check register in tmp/tokens/ if (log) console.log( currentmod," 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 (log) console.log(currentmod,"alias anonymous means not auth"); resnotauth.status = 401; return res.status(resnotauth.status).json(resnotauth); } let tmpfs = `../../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 = `../../tmp/tokens/${alias}.json`; if (action == "clean") { //to reinit bruteforce checker if (log) console.log(currentmod, "try to clean penalty file ", failstamp); fs.remove(failstamp, (err) => { if (err) console.log(Date.now(),currentmod,"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 (log) console.log(currentmod,"penalty:", stamp); await sleep(stamp.numberfail * 100); //increase of 0,1 second the answer time per fail if (log) console.log(currentmod,"time out penalty"); } }; if (!fs.existsSync(tmpfs)) { // need to check detached sign let publickey = ""; const aliasinfo = `../objects/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 (log) console.log(currentmod,"header xalias unknown:",req.session.header.xalias); resnotauth.status = 404; resnotauth.data.xaliasexists = false; return res.status(resnotauth.status).send(resnotauth); } if (log) console.log(currentmod,"publickey", publickey); if (publickey.substring(0, 31) !== "-----BEGIN PGP PUBLIC KEY BLOCK") { console.log(Date.now(),currentmod,"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 (log) console.log(currentmod,"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 (log) console.log(currentmod, "clearmsg", clearmsg); let signedMessage="" const pubkey = await openpgp.readKey({ armoredKey: publickey }); try{ signedMessage = await openpgp.readCleartextMessage({ cleartextMessage: clearmsg, }); }catch(err){ return res.status(422).send({status:422,ref:"Middleware",msg:"unconsistentcleartextmessage",data:{xhash:req.session.header.xhash,clearmsg}}) } const verificationResult = await openpgp.verify({ message: signedMessage, verificationKeys: pubkey, }); if (log) console.log(currentmod,verificationResult); if (log) console.log(currentmod,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 (log) console.log(currentmod,`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 (log) console.log(currentmod,"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) const person = `../../${req.session.header.xtribe}/objects/persons/itm/${req.session.header.xalias}.json`; if (log) console.log(currentmod,"Profils tribe/app management"); if (log) console.log(currentmod,"person", person); if (fs.existsSync(person)) { const infoperson = fs.readJSONSync(person); if (log) console.log(currentmod,"infoperson",infoperson); infoperson.profils.forEach((p) => { if (!req.session.header.xprofils.includes(p)) req.session.header.xprofils.push(p); }) }else{ if (!req.session.header.xprofils.includes('pagans')) req.session.header.xprofils.push("pagans"); } 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"); if (log) console.log(currentmod,`${req.session.header.xalias} Authenticated`); next(); }; module.exports = isAuthenticated;