major update

This commit is contained in:
2023-11-05 12:03:25 +01:00
parent 2edd592ef9
commit 6291d5239e
91 changed files with 6667 additions and 1286 deletions

View File

@@ -22,17 +22,17 @@ const checkHeaders = (req, res, next) => {
* HTTP/1/1 400 Not Found
* {
* status:400,
* ref:"headers"
* ref:"middlewares"
* msg:"missingheaders",
* data: ["headermissing1"]
* data: ["headermissing1"]
* }
*@apiErrorExample {json} Error-Response:
* HTTP/1/1 404 Not Found
* {
* status:404,
* ref:"headers"
* ref:"middlewares"
* msg:"tribeiddoesnotexist",
* data: {xalias}
* data: {xalias}
* }
*
* @apiHeaderExample {json} Header-Exemple:
@@ -61,27 +61,28 @@ const checkHeaders = (req, res, next) => {
missingheader.push(h);
}
}
//console.log( 'header', header )
// console.log( 'pass header', header )
// store in session the header information
req.session.header = header;
// Each header have to be declared
if (missingheader != "") {
// bad request
return res.status(400).json({
ref: "headers",
ref: "middlewares",
msg: "missingheader",
data: missingheader,
});
}
//console.log( req.app.locals.tribeids )
// xtribe == "town" is used during the setup process
// xtribe == "adminapi" is used to access /adminapi
if (
!(
header.xtribe == "town" || req.app.locals.tribeids.includes(header.xtribe)
["town","adminapi"].includes(header.xtribe) || req.app.locals.tribeids.includes(header.xtribe)
)
) {
return res.status(404).json({
ref: "headers",
ref: "middlewares",
msg: "tribeiddoesnotexist",
data: { xtribe: header.xtribe },
});
@@ -90,6 +91,8 @@ const checkHeaders = (req, res, next) => {
console.log("warning language requested does not exist force to english");
header.xlang = "en";
}
//set anonymous profil
req.session.header.xprofils=["anonymous"]
next();
};
module.exports = checkHeaders;

View File

@@ -1,69 +0,0 @@
const fs = require("fs-extra");
const glob = require("glob");
const path = require("path");
const conf = require(`${process.env.dirtown}/conf.json`);
const hasAccessrighton = (object, action, ownby) => {
/*
@action (mandatory) : CRUDO
@object (mandatory)= name of a folder object in /tribeid space can be a tree for example objects/items
@ownby (option) = list des uuid propriétaire
return next() if all action exist in req.app.local.tokens[UUID].ACCESSRIGHTS.data[object]
OR if last action ="O" and uuid exist in ownBy
Careffull if you have many action CRO let O at the end this will force req.right at true if the owner try an action on this object
need to check first a person exist with this alias in tribe
const person = fs.readJsonSync(
`${conf.dirapi}/nationchains/tribes/${req.session.header.xtribe}/persons/${req.session.header.xalias}.json`
);
*/
return (req, res, next) => {
//console.log( 'err.stack hasAccessrights', err.statck )
//console.log( `test accessright on object:${object} for ${req.session.header.xworkon}:`, req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ] )
req.right = false;
if (
req.app.locals.tokens[req.session.header.xpaganid].ACCESSRIGHTS.data[
req.session.header.xworkon
] &&
req.app.locals.tokens[req.session.header.xpaganid].ACCESSRIGHTS.data[
req.session.header.xworkon
][object]
) {
req.right = true;
[...action].forEach((a) => {
if (a == "O" && ownby && ownby.includes(req.session.header.xpaganid)) {
req.right = true;
} else {
req.right =
req.right &&
req.app.locals.tokens[
req.session.header.xpaganid
].ACCESSRIGHTS.data[req.session.header.xworkon][object].includes(a);
}
});
}
//console.log( 'Access data autorise? ', req.right )
if (!req.right) {
return res.status(403).json({
info: "forbiddenAccessright",
ref: "headers",
moreinfo: {
xpaganid: req.session.header.xpaganid,
object: object,
xworkon: req.session.header.xworkon,
action: action,
},
});
}
next();
};
};
module.exports = hasAccessrighton;

View File

@@ -1,118 +1,198 @@
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");
const conf = require(`${process.env.dirtown}/conf.json`);
/**
* 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)
// 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();
console.log(
"if menagedone" + currentday,
!fs.existsSync(`${process.env.dirtown}/tmp/tokensmenagedone${currentday}`)
fs.ensureDirSync(`${process.env.dirtown}/tmp/tokens`);
let menagedone = fs.existsSync(
`${process.env.dirtown}/tmp/tokens/menagedone${currentday}`
);
if (!fs.existsSync(`${process.env.dirtown}/tmp/tokens`))
fs.mkdirSync(`${process.env.dirtown}/tmp/tokens`);
if (!fs.existsSync(`${process.env.dirtown}/tmp/tokensmenagedone${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/tokensmenagedone*`).forEach((f) => {
glob.sync(`${process.env.dirtown}/tmp/tokens/menagedone*`).forEach((f) => {
fs.removeSync(f);
});
glob.sync(`${process.env.dirtown}/tmp/tokens/*.json`).forEach((f) => {
if (tsday - parseInt(f.split("_")[1]) > 86400000) fs.remove(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/
console.log("isAuthenticate?");
if (withlog) console.log("isAuthenticate?", req.session.header, req.body);
const resnotauth = {
ref: "headers",
ref: "middlewares",
msg: "notauthenticated",
data: {
xalias: req.session.header.xalias,
xaliasexists: true,
},
};
//console.log(req.session.header);
if (req.session.header.xalias == "anonymous" || req.session.header.xhash == "anonymous") {
console.log("alias anonymous means not auth");
return res.status(401).json(resnotauth);
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);
}
const tmpfs = `${process.env.dirtown}/tmp/tokens/${req.session.header.xalias}_${
req.session.header.xdays
}_${req.session.header.xhash.substring(20, 200)}`;
//console.log(tmpfs);
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;
if (
fs.existsSync(
`${conf.dirapi}/nationchains/pagans/itm/${req.session.header.xalias}.json`
)
) {
const pagan = fs.readJsonSync(
`${conf.dirapi}/nationchains/pagans/itm/${req.session.header.xalias}.json`
);
publickey = pagan.publicKey;
} else {
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;
if (req.body.publickey) {
publickey = req.body.publickey;
} else {
console.log("alias unknown");
return res.status(404).send(resnotauth);
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);
}
}
if (publickey.substring(0,10)!=="-----BEGIN"){
console.log("Publickey is not valid as armored key:", publickey)
return res.status(404).send(resnotauth);
}
if (Buffer.from(req.session.header.xhash, "base64").toString().substring(0,10)!=="-----BEGIN"){
console.log("xhash conv is not valid as armored key:", Buffer.from(req.session.header.xhash, "base64").toString())
return res.status(404).send(resnotauth);
}
let publicKey;
try {
publicKey = await openpgp.readKey({ armoredKey: publickey });
}catch(err){
console.log(erreur)
}
const msg = await openpgp.createMessage({
text: `${req.session.header.xalias}_${req.session.header.xdays}`,
});
const signature = await openpgp.readSignature({
armoredSignature: Buffer.from(
req.session.header.xhash,
"base64"
).toString(),
});
//console.log(msg);
//console.log(signature);
//console.log(publicKey);
const checkauth = await openpgp.verify({
message: msg,
signature: signature,
verificationKeys: publicKey,
});
//console.log(checkauth);
//console.log(checkauth.signatures[0].keyID);
//console.log(await checkauth.signatures[0].signature);
//console.log(await checkauth.signatures[0].verified);
const { check, keyID } = checkauth.signatures[0];
try {
await check; // raise an error if necessary
fs.outputFileSync(tmpfs, req.session.header.xhash, "utf8");
} catch (e) {
resnotauth.msg = "signaturefailed";
console.log("not auth fail sign");
return res.status(401).send(resnotauth);
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);
}
console.log("Authenticated");
bruteforcepenalty(req.session.header.xalias, "clean");
console.log(`${req.session.header.xalias} Authenticated`);
next();
};
module.exports = isAuthenticated;

View File

@@ -1,7 +0,0 @@
{
"missingheader": "Some header miss to have a valid request: {{#data}} {{.}} {{/data}}",
"tribeiddoesnotexist": "Header xtribe: {{data.xtribe}} does not exist in this town",
"authenticated": "Your alias{{{data.xalias}}} is authenticated",
"notauthenticated": "Your alias: {{data.xalias}} is not authenticated {{^data.aliasexists}} and this alias does not exist !{{/data.aliasexists}}",
"forbiddenAccessright": "Pagan {{data.xalias}} has not access right to act {{data.action}} onto object {{data.object}} for tribe {{mor.xworkon}}"
}