2023-12-07 12:04:19 +01:00
|
|
|
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");
|
|
|
|
|
2023-12-29 13:38:47 +01:00
|
|
|
const l=require('../tools/log.js');
|
|
|
|
l.showlog= false; // force log as well in prod and dev
|
|
|
|
l.context="isAuthenticated";
|
2023-12-07 12:04:19 +01:00
|
|
|
/**
|
|
|
|
* @api {get} http://header/istauthenticated - 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) => {
|
2023-12-29 13:38:47 +01:00
|
|
|
/*
|
2023-12-07 12:04:19 +01:00
|
|
|
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}`
|
|
|
|
);
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og(`menagedone${currentday} was it done today?:${menagedone}`);
|
2023-12-07 12:04:19 +01:00
|
|
|
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]);
|
|
|
|
//24h 86400000 milliseconde 15mn 900000
|
|
|
|
if (elapse && elapse > 86400000) {
|
|
|
|
fs.remove(f);
|
|
|
|
}
|
|
|
|
});
|
2023-12-29 13:38:47 +01:00
|
|
|
//clean tmp
|
|
|
|
glob.sync(`../tmp/*.txt`).forEach((f) => {
|
|
|
|
fs.remove(f);
|
|
|
|
});
|
2023-12-07 12:04:19 +01:00
|
|
|
fs.outputFile(
|
|
|
|
`../tmp/tokens/menagedone${currentday}`,
|
|
|
|
"done by middleware/isAUthenticated"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
//Check register in tmp/tokens/
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("isAuthenticate?", req.session.header, req.body);
|
2023-12-07 12:04:19 +01:00
|
|
|
|
|
|
|
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"
|
|
|
|
) {
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("alias anonymous means not auth");
|
2023-12-07 12:04:19 +01:00
|
|
|
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
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("try to clean penalty file ", failstamp);
|
2023-12-07 12:04:19 +01:00
|
|
|
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);
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("penalty:", stamp);
|
2023-12-07 12:04:19 +01:00
|
|
|
await sleep(stamp.numberfail * 100); //increase of 0,1 second the answer time per fail
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("time out penalty");
|
2023-12-07 12:04:19 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
if (!fs.existsSync(tmpfs)) {
|
|
|
|
// need to check detached sign
|
|
|
|
let publickey = "";
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og(process.cwd());
|
|
|
|
l.og(process.env.PWD);
|
|
|
|
l.og(__dirname);
|
2023-12-07 12:04:19 +01:00
|
|
|
const aliasinfo = `../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 == "") {
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("alias unknown");
|
2023-12-07 12:04:19 +01:00
|
|
|
resnotauth.status = 404;
|
|
|
|
resnotauth.data.xaliasexists = false;
|
|
|
|
return res.status(resnotauth.status).send(resnotauth);
|
|
|
|
}
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("publickey", publickey);
|
2023-12-07 12:04:19 +01:00
|
|
|
if (publickey.substring(0, 31) !== "-----BEGIN PGP PUBLIC KEY BLOCK") {
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("Publickey is not valid as armored key:", publickey);
|
2023-12-07 12:04:19 +01:00
|
|
|
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") {
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("xhash conv is not valid as armored key:", clearmsg);
|
2023-12-07 12:04:19 +01:00
|
|
|
await bruteforcepenalty(req.session.header.xalias, "penalty");
|
|
|
|
resnotauth.status = 404;
|
|
|
|
return res.status(resnotauth.status).send(resnotauth);
|
|
|
|
}
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("clearmsg", clearmsg);
|
|
|
|
let signedMessage=""
|
2023-12-07 12:04:19 +01:00
|
|
|
const pubkey = await openpgp.readKey({ armoredKey: publickey });
|
2023-12-29 13:38:47 +01:00
|
|
|
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}})
|
|
|
|
}
|
2023-12-07 12:04:19 +01:00
|
|
|
const verificationResult = await openpgp.verify({
|
|
|
|
message: signedMessage,
|
|
|
|
verificationKeys: pubkey,
|
|
|
|
});
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og(verificationResult);
|
|
|
|
l.og(verificationResult.signatures[0].keyID.toHex());
|
2023-12-07 12:04:19 +01:00
|
|
|
try {
|
|
|
|
await verificationResult.signatures[0].verified;
|
|
|
|
if (
|
|
|
|
verificationResult.data !=
|
|
|
|
`${req.session.header.xalias}_${req.session.header.xdays}`
|
|
|
|
) {
|
|
|
|
resnotauth.msg = "signaturefailled";
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og(
|
2023-12-07 12:04:19 +01:00
|
|
|
`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";
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("erreur", e);
|
2023-12-07 12:04:19 +01:00
|
|
|
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 = `${process.env.dirtown}/tribes/${req.session.header.xtribe}/objects/persons/itm/${req.session.header.xalias}.json`;
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og("Profils tribe/app management");
|
|
|
|
l.og("person", person);
|
2023-12-07 12:04:19 +01:00
|
|
|
if (fs.existsSync(person)) {
|
|
|
|
const infoperson = fs.readJSONSync(person);
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og(infoperson);
|
2023-12-07 12:04:19 +01:00
|
|
|
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");
|
2023-12-29 13:38:47 +01:00
|
|
|
l.og(`${req.session.header.xalias} Authenticated`);
|
2023-12-07 12:04:19 +01:00
|
|
|
next();
|
|
|
|
};
|
|
|
|
module.exports = isAuthenticated;
|