apxtrib/api/middlewares/isAuthenticated.js

221 lines
8.5 KiB
JavaScript
Raw Normal View History

2023-04-27 04:17:20 +00:00
const fs = require("fs-extra");
const dayjs = require("dayjs");
const glob = require("glob");
2023-11-05 11:03:25 +00:00
// To debug it could be easier with source code:
// const openpgp = require("/media/phil/usbfarm/apxtrib/node_modules/openpgp/dist/node/openpgp.js");
2023-05-12 05:59:32 +00:00
const openpgp = require("openpgp");
2023-04-27 04:17:20 +00:00
2023-11-05 11:03:25 +00:00
/**
2023-11-19 15:34:37 +00:00
* @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
2023-11-05 11:03:25 +00:00
*
2023-11-19 15:34:37 +00:00
* @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
2023-11-05 11:03:25 +00:00
*
2023-11-19 15:34:37 +00:00
* @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"
* }
**/
2023-05-12 05:59:32 +00:00
const isAuthenticated = async (req, res, next) => {
2023-11-05 11:03:25 +00:00
const withlog = true;
2023-04-27 04:17:20 +00:00
const currentday = dayjs().date();
2023-11-05 11:03:25 +00:00
fs.ensureDirSync(`${process.env.dirtown}/tmp/tokens`);
let menagedone = fs.existsSync(
`${process.env.dirtown}/tmp/tokens/menagedone${currentday}`
2023-04-27 04:17:20 +00:00
);
2023-11-05 11:03:25 +00:00
if (withlog)
console.log(`menagedone${currentday} was it done today?:${menagedone}`);
if (!menagedone) {
2023-04-27 04:17:20 +00:00
// clean oldest
2023-05-12 05:59:32 +00:00
const tsday = dayjs().valueOf(); // now in timestamp format
2023-11-05 11:03:25 +00:00
glob.sync(`${process.env.dirtown}/tmp/tokens/menagedone*`).forEach((f) => {
2023-04-27 04:17:20 +00:00
fs.removeSync(f);
});
2023-05-16 08:31:27 +00:00
glob.sync(`${process.env.dirtown}/tmp/tokens/*.json`).forEach((f) => {
2023-11-05 11:03:25 +00:00
const fsplit = f.split("_");
const elapse = tsday - parseInt(fsplit[2]);
//24h 86400000 milliseconde 15mn 900000
if (elapse && elapse > 86400000) {
fs.remove(f);
}
2023-04-27 04:17:20 +00:00
});
2023-11-05 11:03:25 +00:00
fs.outputFile(
`${process.env.dirtown}/tmp/tokens/menagedone${currentday}`,
"done by middleware/isAUthenticated"
);
2023-04-27 04:17:20 +00:00
}
//Check register in tmp/tokens/
2023-11-05 11:03:25 +00:00
if (withlog) console.log("isAuthenticate?", req.session.header, req.body);
2023-04-27 04:17:20 +00:00
const resnotauth = {
2023-11-05 11:03:25 +00:00
ref: "middlewares",
2023-04-27 04:17:20 +00:00
msg: "notauthenticated",
data: {
xalias: req.session.header.xalias,
2023-05-12 05:59:32 +00:00
xaliasexists: true,
2023-04-27 04:17:20 +00:00
},
};
2023-11-05 11:03:25 +00:00
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);
2023-05-12 05:59:32 +00:00
}
2023-04-27 04:17:20 +00:00
2023-11-05 11:03:25 +00:00
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");
}
};
2023-05-12 05:59:32 +00:00
if (!fs.existsSync(tmpfs)) {
// need to check detached sign
2023-11-05 11:03:25 +00:00
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) {
2023-05-12 05:59:32 +00:00
resnotauth.data.xaliasexists = false;
2023-11-05 11:03:25 +00:00
publickey = req.body.publickey;
2023-04-27 04:17:20 +00:00
}
2023-11-05 11:03:25 +00:00
if (publickey == "") {
if (withlog) console.log("alias unknown");
resnotauth.status = 404;
resnotauth.data.xaliasexists = false;
return res.status(resnotauth.status).send(resnotauth);
2023-06-02 08:21:01 +00:00
}
2023-11-05 11:03:25 +00:00
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);
2023-06-02 08:21:01 +00:00
}
2023-11-05 11:03:25 +00:00
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);
2023-06-02 08:21:01 +00:00
}
2023-11-05 11:03:25 +00:00
if (withlog) console.log("clearmsg", clearmsg);
const pubkey = await openpgp.readKey({ armoredKey: publickey });
const signedMessage = await openpgp.readCleartextMessage({
cleartextMessage: clearmsg,
2023-05-12 05:59:32 +00:00
});
2023-11-05 11:03:25 +00:00
const verificationResult = await openpgp.verify({
message: signedMessage,
verificationKeys: pubkey,
2023-05-12 05:59:32 +00:00
});
2023-11-05 11:03:25 +00:00
if (withlog) console.log(verificationResult);
if (withlog) console.log(verificationResult.signatures[0].keyID.toHex());
2023-05-12 05:59:32 +00:00
try {
2023-11-05 11:03:25 +00:00
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);
}
2023-05-12 05:59:32 +00:00
} catch (e) {
2023-11-05 11:03:25 +00:00
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));
2023-05-12 05:59:32 +00:00
}
2023-11-05 11:03:25 +00:00
fs.outputJSONSync(tmpfs, req.session.header.xprofils);
} else {
//tmpfs exist get profils from identification process
req.session.header.xprofils = fs.readJSONSync(tmpfs);
2023-04-27 04:17:20 +00:00
}
2023-11-05 11:03:25 +00:00
bruteforcepenalty(req.session.header.xalias, "clean");
console.log(`${req.session.header.xalias} Authenticated`);
2023-05-12 05:59:32 +00:00
next();
2023-04-27 04:17:20 +00:00
};
module.exports = isAuthenticated;