major update
This commit is contained in:
@@ -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;
|
||||
|
@@ -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;
|
@@ -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;
|
||||
|
@@ -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}}"
|
||||
}
|
Reference in New Issue
Block a user