add middleware security to prevent injection
This commit is contained in:
+147
-34
@@ -43,6 +43,14 @@ SEE README.md to start
|
||||
********************************************/
|
||||
const apxtri = {};
|
||||
|
||||
apxtri.istribe = (tribe) => {
|
||||
if (typeof tribe !== "string" || !/^[a-z0-9_-]{1,64}$/.test(tribe)) {
|
||||
return false;
|
||||
}
|
||||
if (tribe === "adminapi") return true;
|
||||
return fs.existsSync(`../${tribe}/apxtri/routes`);
|
||||
};
|
||||
|
||||
apxtri.main = async () => {
|
||||
if (process.env.NODE_MODE !== 'dev' && !fs.existsSync("/etc/nginx/nginx.conf")) {
|
||||
console.log(
|
||||
@@ -51,39 +59,97 @@ apxtri.main = async () => {
|
||||
process.exit(0);
|
||||
}
|
||||
//Check prerequest data
|
||||
if (
|
||||
fs.existsSync("../adminapi/objects/tribes/idx/tribes_dns.json") &&
|
||||
fs.existsSync("../adminapi/objects/tribes/itm/adminapi.json")
|
||||
) {
|
||||
// check all tribes are in tribes_dns.json
|
||||
const conf = fs.readJSONSync(
|
||||
"../adminapi/objects/tribes/itm/adminapi.json"
|
||||
const adminapipath = "../adminapi/objects/tribes/itm/adminapi.json";
|
||||
const tribednspath = "../adminapi/objects/tribes/idx/tribes_dns.json";
|
||||
// An existing adminapi.json means this is not a first install: never
|
||||
// overwrite it from the template, even if tribes_dns.json is missing.
|
||||
if (fs.existsSync(adminapipath)) {
|
||||
// check all tribes are in tribes_dns.json (rebuild it if needed)
|
||||
const conf = fs.readJSONSync(adminapipath);
|
||||
const tribesdns = fs.existsSync(tribednspath)
|
||||
? fs.readJsonSync(tribednspath)
|
||||
: {};
|
||||
Object.keys(tribesdns).forEach((tribe) => {
|
||||
if (!apxtri.istribe(tribe)) {
|
||||
console.log(
|
||||
`\x1b[31m drop unknown tribe from tribes_dns.json: ${tribe}\x1b[0m`
|
||||
);
|
||||
delete tribesdns[tribe];
|
||||
}
|
||||
});
|
||||
// A partial/restored adminapi.json must not crash the whole server: fill
|
||||
// the runtime configuration from the install template and warn loudly.
|
||||
const initconf = fs.readJsonSync(
|
||||
"../adminapi/apxtri/setup/initadminapi.json"
|
||||
);
|
||||
const tribesdns = fs.readJsonSync(
|
||||
`../adminapi/objects/tribes/idx/tribes_dns.json`
|
||||
);
|
||||
//check if new tribe was add
|
||||
glob
|
||||
if (!conf.api) {
|
||||
conf.api = JSON.parse(JSON.stringify(initconf.api));
|
||||
console.log(
|
||||
"\x1b[31m adminapi.json is missing 'api', using defaults from apxtri/setup/initadminapi.json. Restore this file from a backup.\x1b[0m"
|
||||
);
|
||||
}
|
||||
if (!conf.socket) {
|
||||
conf.socket = JSON.parse(JSON.stringify(initconf.socket));
|
||||
console.log(
|
||||
"\x1b[31m adminapi.json is missing 'socket', using default port from apxtri/setup/initadminapi.json.\x1b[0m"
|
||||
);
|
||||
}
|
||||
//check if new tribe was add (only real tribe folders, never tmp/.git/...)
|
||||
const tribedirs = glob
|
||||
.sync("../*")
|
||||
.filter((f) => fs.lstatSync(f).isDirectory())
|
||||
.forEach(async (t) => {
|
||||
const tribe = path.basename(t);
|
||||
//console.log(tribe);
|
||||
if (![".", ".."].includes(tribe) && !tribesdns[tribe]) {
|
||||
await apxtri.setuptribe(tribe, conf);
|
||||
.filter((f) => fs.existsSync(`${f}/apxtri/routes`));
|
||||
for (const t of tribedirs) {
|
||||
const tribe = path.basename(t);
|
||||
if ([".", ".."].includes(tribe) || tribesdns[tribe]) continue;
|
||||
// Never re-initialize an already configured tribe: if its itm file
|
||||
// exists, just restore its dns entry instead of overwriting the config.
|
||||
const itmpath = `../adminapi/objects/tribes/itm/${tribe}.json`;
|
||||
if (fs.existsSync(itmpath)) {
|
||||
try {
|
||||
const existing = fs.readJSONSync(itmpath);
|
||||
if (Array.isArray(existing.dns) && existing.dns.length > 0) {
|
||||
tribesdns[tribe] = existing.dns;
|
||||
console.log(
|
||||
`tribe ${tribe} already configured, restored in tribes_dns.json`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`unreadable itm/${tribe}.json, skipping:`,
|
||||
err.message
|
||||
);
|
||||
continue;
|
||||
}
|
||||
});
|
||||
}
|
||||
await apxtri.setuptribe(tribe, conf);
|
||||
}
|
||||
// merge with the on-disk index so setuptribe writes are preserved
|
||||
const ondiskdns = fs.existsSync(tribednspath)
|
||||
? fs.readJsonSync(tribednspath)
|
||||
: {};
|
||||
Object.keys(ondiskdns).forEach((tribe) => {
|
||||
if (!apxtri.istribe(tribe)) delete ondiskdns[tribe];
|
||||
});
|
||||
fs.outputJSONSync(
|
||||
tribednspath,
|
||||
Object.assign({}, ondiskdns, tribesdns),
|
||||
{ space: 2 }
|
||||
);
|
||||
// run api with the (possibly repaired) in-memory conf
|
||||
await apxtri.runexpress(fs.readJsonSync(tribednspath), conf);
|
||||
} else {
|
||||
const initconf = fs.readJsonSync(
|
||||
"../adminapi/apxtri/setup/initadminapi.json"
|
||||
);
|
||||
await apxtri.setuptribe("adminapi", initconf);
|
||||
// run api with update conf
|
||||
await apxtri.runexpress(
|
||||
fs.readJsonSync(`../adminapi/objects/tribes/idx/tribes_dns.json`),
|
||||
fs.readJSONSync("../adminapi/objects/tribes/itm/adminapi.json")
|
||||
);
|
||||
}
|
||||
// run api with update conf
|
||||
apxtri.runexpress(
|
||||
fs.readJsonSync(`../adminapi/objects/tribes/idx/tribes_dns.json`),
|
||||
fs.readJSONSync("../adminapi/objects/tribes/itm/adminapi.json")
|
||||
);
|
||||
};
|
||||
apxtri.getip = async () => {
|
||||
/**
|
||||
@@ -112,6 +178,21 @@ apxtri.getip = async () => {
|
||||
return network;
|
||||
};
|
||||
apxtri.setuptribe = async (tribe, conf) => {
|
||||
if (!apxtri.istribe(tribe)) {
|
||||
console.log(
|
||||
`\x1b[31m skip setuptribe: ${tribe} is not an existing tribe folder\x1b[0m`
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (tribe != "adminapi") {
|
||||
const existingitm = `../adminapi/objects/tribes/itm/${tribe}.json`;
|
||||
if (fs.existsSync(existingitm)) {
|
||||
console.log(
|
||||
`\x1b[31m skip setuptribe: ${tribe} is already configured\x1b[0m`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let inittribe;
|
||||
if (tribe == "adminapi") {
|
||||
console.log(
|
||||
@@ -131,9 +212,13 @@ apxtri.setuptribe = async (tribe, conf) => {
|
||||
}
|
||||
} else {
|
||||
console.log(`a new tribe called ${tribe} was detected`);
|
||||
const adminapiconf = fs.readJSONSync(
|
||||
"../adminapi/objects/tribes/itm/adminapi.json"
|
||||
const initconf = fs.readJsonSync(
|
||||
"../adminapi/apxtri/setup/initadminapi.json"
|
||||
);
|
||||
const adminapiconf =
|
||||
conf && conf.api && conf.socket
|
||||
? conf
|
||||
: fs.readJSONSync("../adminapi/objects/tribes/itm/adminapi.json");
|
||||
inittribe = {
|
||||
tribeId: tribe,
|
||||
townpath: __dirname.replace("/adminapi/apxtri", ""),
|
||||
@@ -142,8 +227,8 @@ apxtri.setuptribe = async (tribe, conf) => {
|
||||
nationId: conf.nationId,
|
||||
townId: conf.townId,
|
||||
activelog: [],
|
||||
api: { port: adminapiconf.api.port },
|
||||
socket: { port: adminapiconf.socket.port },
|
||||
api: { port: (adminapiconf.api || initconf.api).port },
|
||||
socket: { port: (adminapiconf.socket || initconf.socket).port },
|
||||
};
|
||||
}
|
||||
inittribe.sudoUser = process.env.USER;
|
||||
@@ -171,7 +256,7 @@ apxtri.setuptribe = async (tribe, conf) => {
|
||||
let lsttribe = fs.existsSync(lst_tribeIdpath)
|
||||
? fs.readJsonSync(lst_tribeIdpath)
|
||||
: [];
|
||||
lsttribe.push(tribe);
|
||||
if (!lsttribe.includes(tribe)) lsttribe.push(tribe);
|
||||
fs.outputJSONSync(lst_tribeIdpath, lsttribe);
|
||||
const tribes_dnspath = "../adminapi/objects/tribes/idx/tribes_dns.json";
|
||||
const tribedns = fs.existsSync(tribes_dnspath)
|
||||
@@ -248,7 +333,12 @@ apxtri.setuptribe = async (tribe, conf) => {
|
||||
|
||||
apxtri.runexpress = async (tribesdns, conf) => {
|
||||
const Odmdb = require(path.resolve("./apxtri/models/Odmdb.js"));
|
||||
let tribeIds = Object.keys(tribesdns);
|
||||
const checkRequestSafety = require(
|
||||
path.resolve("./apxtri/middlewares/checkRequestSafety.js")
|
||||
);
|
||||
let tribeIds = Object.keys(tribesdns).filter((tribe) =>
|
||||
apxtri.istribe(tribe)
|
||||
);
|
||||
// context is store in /itm/tribename.json ={contexte:{routes:[],models:[{model:,tplstringslg:[]}]}
|
||||
// routes={url,route} check how to add plugin tribe route later
|
||||
// keep only the 2 last part (.) of domain name to validate requests with it (generic domain)
|
||||
@@ -260,6 +350,13 @@ apxtri.runexpress = async (tribesdns, conf) => {
|
||||
if (!doms.includes(dm)) doms.push(dm);
|
||||
//reindex database attention check dev-ants/.. a bug was fixed
|
||||
glob.sync(`../${t}/objects/*`).forEach((o) => {
|
||||
if (!fs.existsSync(o) || !fs.lstatSync(o).isDirectory()) return;
|
||||
if (
|
||||
!fs.existsSync(path.join(o, "itm")) &&
|
||||
!fs.existsSync(path.join(o, "conf.json"))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
console.log("reindex: ", o);
|
||||
Odmdb.runidx(o);
|
||||
});
|
||||
@@ -292,18 +389,34 @@ apxtri.runexpress = async (tribesdns, conf) => {
|
||||
});
|
||||
const app = express();
|
||||
// load express parameter from conf
|
||||
Object.keys(conf.api.appset).forEach((p) => {
|
||||
app.set(p, conf.api.appset[p]);
|
||||
const apiconf = conf.api || {};
|
||||
if (apiconf.appset && apiconf.appset["trust proxy"] === true) {
|
||||
apiconf.appset["trust proxy"] = 1;
|
||||
console.log(
|
||||
"\x1b[31m api.appset['trust proxy']=true is unsafe, using 1 (nginx only)\x1b[0m"
|
||||
);
|
||||
}
|
||||
Object.keys(apiconf.appset || {}).forEach((p) => {
|
||||
app.set(p, apiconf.appset[p]);
|
||||
});
|
||||
// To set depending of data form or get size to send
|
||||
tribeIds.forEach((t) => {
|
||||
app.use(`/${t}`, bodyParser.urlencoded(conf.api.bodyparse.urlencoded));
|
||||
app.use(`/${t}`, bodyParser.json(conf.api.bodyparse.json));
|
||||
app.use(
|
||||
`/${t}`,
|
||||
bodyParser.urlencoded(
|
||||
(apiconf.bodyparse && apiconf.bodyparse.urlencoded) || {},
|
||||
),
|
||||
);
|
||||
app.use(
|
||||
`/${t}`,
|
||||
bodyParser.json((apiconf.bodyparse && apiconf.bodyparse.json) || {}),
|
||||
);
|
||||
// To set depending of post put json data size to send
|
||||
app.use(`/${t}`, express.json(conf.api.json));
|
||||
app.use(`/${t}`, express.json(apiconf.json || {}));
|
||||
});
|
||||
app.disable("x-powered-by"); // for security
|
||||
app.locals.tribeids = tribeIds;
|
||||
app.use(checkRequestSafety);
|
||||
const currentmod = "apxtri";
|
||||
const log = process.env.ACTIVELOG ? process.env.ACTIVELOG.split(',').includes(currentmod) : false;
|
||||
console.log(
|
||||
|
||||
@@ -60,6 +60,14 @@ const log = process.env.ACTIVELOG ? process.env.ACTIVELOG.split(',').includes(cu
|
||||
* data: {xalias}
|
||||
* }
|
||||
*/
|
||||
const aliaspattern = /^[a-z0-9]{3,64}$/;
|
||||
const namepattern = /^[A-Za-z0-9_.:-]{1,64}$/;
|
||||
const indexpattern = /^[A-Za-z0-9_.-]{1,128}$/;
|
||||
const uuidpattern =
|
||||
/^(0|[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
|
||||
const emailpattern =
|
||||
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
|
||||
const checkHeaders = (req, res, next) => {
|
||||
req.session = {};
|
||||
const header = {};
|
||||
@@ -90,14 +98,118 @@ const checkHeaders = (req, res, next) => {
|
||||
data: {missingheader},
|
||||
});
|
||||
}
|
||||
const allowedtribes = [
|
||||
"town",
|
||||
"adminapi",
|
||||
...(Array.isArray(req.app.locals.tribeids) ? req.app.locals.tribeids : []),
|
||||
];
|
||||
const invalidheader = (field, value) =>
|
||||
res.status(400).json({
|
||||
status: 400,
|
||||
ref: "middlewares",
|
||||
msg: "headervalueinvalid",
|
||||
data: { field, value },
|
||||
});
|
||||
if (header.xalias !== "anonymous" && !aliaspattern.test(header.xalias)) {
|
||||
return invalidheader("xalias", header.xalias);
|
||||
}
|
||||
if (!uuidpattern.test(header.xuuid)) {
|
||||
return invalidheader("xuuid", header.xuuid);
|
||||
}
|
||||
if (
|
||||
header.xapp === "." ||
|
||||
header.xapp === ".." ||
|
||||
!namepattern.test(header.xapp)
|
||||
) {
|
||||
return invalidheader("xapp", header.xapp);
|
||||
}
|
||||
if (header.xdays !== undefined && !/^\d{0,16}$/.test(header.xdays)) {
|
||||
return invalidheader("xdays", header.xdays);
|
||||
}
|
||||
if (
|
||||
header.xtrkversion !== undefined &&
|
||||
!/^\d{0,10}$/.test(header.xtrkversion)
|
||||
) {
|
||||
return invalidheader("xtrkversion", header.xtrkversion);
|
||||
}
|
||||
if (
|
||||
req.headers.xworkon !== undefined &&
|
||||
!allowedtribes.includes(req.headers.xworkon)
|
||||
) {
|
||||
return res.status(404).json({
|
||||
status: 404,
|
||||
ref: "middlewares",
|
||||
msg: "tribeiddoesnotexist",
|
||||
data: { xworkon: req.headers.xworkon },
|
||||
});
|
||||
}
|
||||
const invalidparam = (param, value) =>
|
||||
res.status(400).json({
|
||||
status: 400,
|
||||
ref: "middlewares",
|
||||
msg: "paramvalueinvalid",
|
||||
data: { param, value },
|
||||
});
|
||||
for (const param of ["tribe", "tribeId", "tribeid"]) {
|
||||
if (
|
||||
req.params[param] !== undefined &&
|
||||
!allowedtribes.includes(req.params[param])
|
||||
) {
|
||||
return res.status(404).json({
|
||||
status: 404,
|
||||
ref: "middlewares",
|
||||
msg: "tribeiddoesnotexist",
|
||||
data: { tribe: req.params[param] },
|
||||
});
|
||||
}
|
||||
}
|
||||
const paramrules = {
|
||||
alias: aliaspattern,
|
||||
objectname: indexpattern,
|
||||
optionname: indexpattern,
|
||||
indexname: indexpattern,
|
||||
primaryindex: /^[A-Za-z0-9_.@+-]{1,128}$/,
|
||||
primaryid: /^[A-Za-z0-9_.@+-]{1,128}$/,
|
||||
apxid: /^[A-Za-z0-9_]{1,64}$/,
|
||||
articleid: indexpattern,
|
||||
evaluation: /^(follow|[0-4])$/,
|
||||
app: namepattern,
|
||||
key: namepattern,
|
||||
jobadid: indexpattern,
|
||||
jobstepid: indexpattern,
|
||||
personalias: aliaspattern,
|
||||
recruiter: aliaspattern,
|
||||
seeker: aliaspattern,
|
||||
profil: namepattern,
|
||||
wconame: namepattern,
|
||||
appname: namepattern,
|
||||
pagename: namepattern,
|
||||
website: namepattern,
|
||||
template: namepattern,
|
||||
mlist: namepattern,
|
||||
srckey: namepattern,
|
||||
action: namepattern,
|
||||
pluginname: namepattern,
|
||||
filename: namepattern,
|
||||
email: emailpattern,
|
||||
siret: /^\d{14}$/,
|
||||
typekey: /^[A-Za-z0-9_.-]{1,32}$/,
|
||||
data: /^[A-Za-z0-9_.@+-]{1,128}$/,
|
||||
validation: /^[A-Za-z0-9_-]{1,128}$/,
|
||||
digit6: /^\d{1,6}$/,
|
||||
version: /^\d{1,16}$/,
|
||||
};
|
||||
for (const param of Object.keys(paramrules)) {
|
||||
const value = req.params[param];
|
||||
if (value === undefined || value === "") continue;
|
||||
if (value === "." || value === ".." || !paramrules[param].test(value)) {
|
||||
return invalidparam(param, value);
|
||||
}
|
||||
}
|
||||
//console.log( req.app.locals.tribeids )
|
||||
// xtribe == "town" is used during the setup process
|
||||
// xtribe == "adminapi" is used to access /adminapi
|
||||
if (
|
||||
!(
|
||||
["town","adminapi"].includes(header.xtribe) || req.app.locals.tribeids.includes(header.xtribe)
|
||||
)
|
||||
) {
|
||||
if (!allowedtribes.includes(header.xtribe)) {
|
||||
return res.status(404).json({
|
||||
status:404,
|
||||
ref: "middlewares",
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
const Security = require("../models/Security.js");
|
||||
|
||||
const currentmod = "checkRequestSafety";
|
||||
const log = process.env.ACTIVELOG
|
||||
? process.env.ACTIVELOG.split(",").includes(currentmod)
|
||||
: false;
|
||||
|
||||
const pathHeaders = ["xtribe", "xapp", "xuuid", "xalias", "xworkon"];
|
||||
const enforce = process.env.SECURITY_TRAVERSAL_ENFORCE === "true";
|
||||
|
||||
const collect = (value, field, findings, depth = 0) => {
|
||||
if (depth > 8 || findings.length >= 5) return;
|
||||
if (typeof value === "string") {
|
||||
if (Security.freeTextFields.includes(field)) return;
|
||||
if (/^imgbase64_/i.test(field)) return;
|
||||
if (Security.isTraversal(value)) findings.push({ field, value });
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value.slice(0, 100).forEach((item) => collect(item, field, findings, depth + 1));
|
||||
return;
|
||||
}
|
||||
if (value && typeof value === "object") {
|
||||
Object.keys(value)
|
||||
.slice(0, 100)
|
||||
.forEach((key) => collect(value[key], key, findings, depth + 1));
|
||||
}
|
||||
};
|
||||
|
||||
const checkRequestSafety = (req, res, next) => {
|
||||
const findings = [];
|
||||
collect(req.originalUrl, "url", findings);
|
||||
collect(req.path, "path", findings);
|
||||
collect(req.params, "params", findings);
|
||||
collect(req.query, "query", findings);
|
||||
collect(req.body, "body", findings);
|
||||
pathHeaders.forEach((header) => {
|
||||
const value = req.headers[header];
|
||||
if (value !== undefined && Security.isTraversal(value)) {
|
||||
findings.push({ field: header, value });
|
||||
}
|
||||
});
|
||||
|
||||
const unique = [];
|
||||
findings.forEach((finding) => {
|
||||
const key = `${finding.field}|${finding.value}`;
|
||||
if (!unique.some((item) => `${item.field}|${item.value}` === key)) {
|
||||
unique.push(finding);
|
||||
}
|
||||
});
|
||||
|
||||
if (unique.length > 0) {
|
||||
if (log) console.log(currentmod, "suspicious request", unique);
|
||||
unique.forEach((finding) => Security.alert(req, finding));
|
||||
if (enforce) {
|
||||
return res.status(400).json({
|
||||
status: 400,
|
||||
ref: "middlewares",
|
||||
msg: "invalidrequest",
|
||||
data: { fields: unique.map((finding) => finding.field) },
|
||||
});
|
||||
}
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = checkRequestSafety;
|
||||
@@ -117,13 +117,33 @@ const isAuthenticated = async (req, res, next) => {
|
||||
if (log) console.log(currentmod,"time out penalty");
|
||||
}
|
||||
};
|
||||
const paganpath = `../adminapi/objects/pagans/itm/${req.session.header.xalias}.json`;
|
||||
const paganexists = fs.existsSync(paganpath);
|
||||
const base = req.baseUrl || "";
|
||||
const ispaganroute = /\/pagans\/?$/.test(base);
|
||||
const iscreatepagan =
|
||||
req.method === "POST" &&
|
||||
ispaganroute &&
|
||||
(req.path === "/" || req.path === "");
|
||||
if (!paganexists && !iscreatepagan) {
|
||||
resnotauth.status = 401;
|
||||
resnotauth.data.xaliasexists = false;
|
||||
return res.status(resnotauth.status).json(resnotauth);
|
||||
}
|
||||
if (iscreatepagan && req.body.alias !== req.session.header.xalias) {
|
||||
return res.status(400).json({
|
||||
status: 400,
|
||||
ref: "Middlewares",
|
||||
msg: "aliasmismatch",
|
||||
data: { xalias: req.session.header.xalias, alias: req.body.alias },
|
||||
});
|
||||
}
|
||||
if (!fs.existsSync(tmpfs)) {
|
||||
// need to check detached sign
|
||||
let publickey = "";
|
||||
const aliasinfo = `../adminapi/objects/pagans/itm/${req.session.header.xalias}.json`;
|
||||
if (fs.existsSync(aliasinfo)) {
|
||||
publickey = fs.readJsonSync(aliasinfo).publickey;
|
||||
} else if (req.body.publickey) {
|
||||
if (paganexists) {
|
||||
publickey = fs.readJsonSync(paganpath).publickey;
|
||||
} else if (iscreatepagan && req.body.publickey) {
|
||||
resnotauth.data.xaliasexists = false;
|
||||
publickey = req.body.publickey;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,8 @@ Checkjson.schema.properties.format = {
|
||||
ipv4: /^([0–9]{1,3}.){3}.([0–9]{1,3})$/,
|
||||
ipv6: /^((([0–9A-Fa-f]{1,4}:){7}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){6}:[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){5}:([0–9A-Fa-f]{1,4}:)?[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){4}:([0–9A-Fa-f]{1,4}:){0,2}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){3}:([0–9A-Fa-f]{1,4}:){0,3}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){2}:([0–9A-Fa-f]{1,4}:){0,4}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){6}((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|(([0–9A-Fa-f]{1,4}:){0,5}:((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|(::([0–9A-Fa-f]{1,4}:){0,5}((b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b).){3}(b((25[0–5])|(1d{2})|(2[0–4]d)|(d{1,2}))b))|([0–9A-Fa-f]{1,4}::([0–9A-Fa-f]{1,4}:){0,5}[0–9A-Fa-f]{1,4})|(::([0–9A-Fa-f]{1,4}:){0,6}[0–9A-Fa-f]{1,4})|(([0–9A-Fa-f]{1,4}:){1,7}:))$/,
|
||||
telephonefr: /^0[1-9][0-9]{8}$/,
|
||||
imgbase64: /data:image\/(png|jpg|jpeg|svg);base64,(?:[A-Za-z0-9+\/]+)/,
|
||||
imgbase64:
|
||||
/^data:image\/(png|jpeg|jpg|webp|gif);base64,[A-Za-z0-9+/]+={0,2}$/i,
|
||||
telephoneinter: /^\+*(\d{3})*[0-9,\-]{8,}/,
|
||||
password:
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/,
|
||||
|
||||
@@ -21,7 +21,18 @@ const log = process.env.ACTIVELOG
|
||||
const Notifications = {};
|
||||
|
||||
Notifications.get = (alias, tribeId) => {
|
||||
const notiffile = `../${req.params.tribe}/notifications/${req.params.alias}.json`;
|
||||
if (
|
||||
!/^[a-z0-9]{3,64}$/.test(String(alias)) ||
|
||||
!/^[a-z0-9_-]{1,64}$/.test(String(tribeId))
|
||||
) {
|
||||
return {
|
||||
status: 400,
|
||||
ref: "Notification",
|
||||
msg: "paramsinvalid",
|
||||
data: { alias, tribeId },
|
||||
};
|
||||
}
|
||||
const notiffile = `../${tribeId}/notifications/${alias}.json`;
|
||||
const msg = fs.existsSync(notiffile) ? fs.readJSONSync(notiffile) : {};
|
||||
return {
|
||||
status: 200,
|
||||
@@ -50,8 +61,6 @@ Notifications.statmaillist = (tribe) => {
|
||||
});
|
||||
});
|
||||
});
|
||||
// fichier csv stocker en local en attendant d'avoir un back pour stocker la reponse dans data.csv
|
||||
fs.outputFileSync(`../${tribe}/mailinglst.csv`, csv, "utf-8");
|
||||
return {
|
||||
status: 200,
|
||||
ref: "Notifications",
|
||||
|
||||
+170
-35
@@ -625,6 +625,145 @@ Odmdb.indexAllowed = (indexaccess, role) => {
|
||||
|
||||
* */
|
||||
|
||||
Odmdb.prepareUploadedImages = async (objectPathname, itm) => {
|
||||
const getschema = Odmdb.Schema(objectPathname, true);
|
||||
if (getschema.status != 200) return getschema;
|
||||
const schema = getschema.data.schema;
|
||||
const imagekeys = Object.keys(itm).filter(
|
||||
(key) => key.startsWith("imgbase64_") && itm[key] !== ""
|
||||
);
|
||||
if (imagekeys.length == 0) {
|
||||
return {
|
||||
status: 200,
|
||||
ref: "Odmdb",
|
||||
msg: "imagesprepared",
|
||||
data: { written: [] },
|
||||
};
|
||||
}
|
||||
const apxidvalue = itm[schema.apxid];
|
||||
if (!apxidvalue) {
|
||||
return {
|
||||
status: 406,
|
||||
ref: "Odmdb",
|
||||
msg: "apxidmissing",
|
||||
data: { missingkey: schema.apxid },
|
||||
};
|
||||
}
|
||||
const allowed = /^data:image\/(png|jpeg|jpg|webp|gif);base64,([A-Za-z0-9+/=\s]+)$/i;
|
||||
const validmagic = (buffer) => {
|
||||
if (buffer.length < 12) return false;
|
||||
const ispng =
|
||||
buffer[0] === 0x89 &&
|
||||
buffer[1] === 0x50 &&
|
||||
buffer[2] === 0x4e &&
|
||||
buffer[3] === 0x47 &&
|
||||
buffer[4] === 0x0d &&
|
||||
buffer[5] === 0x0a &&
|
||||
buffer[6] === 0x1a &&
|
||||
buffer[7] === 0x0a;
|
||||
const isjpeg =
|
||||
buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff;
|
||||
const gif = buffer.slice(0, 6).toString();
|
||||
const isgif = gif === "GIF87a" || gif === "GIF89a";
|
||||
const iswebp =
|
||||
buffer.slice(0, 4).toString() === "RIFF" &&
|
||||
buffer.slice(8, 12).toString() === "WEBP";
|
||||
return ispng || isjpeg || isgif || iswebp;
|
||||
};
|
||||
const sharp = require("sharp");
|
||||
const objectname = path.basename(objectPathname);
|
||||
const imgdir = path.resolve(objectPathname, "img");
|
||||
const safeid = String(apxidvalue)
|
||||
.replace(/[^A-Za-z0-9_-]/g, "_")
|
||||
.slice(0, 64);
|
||||
const writes = [];
|
||||
for (const key of imagekeys) {
|
||||
if (typeof itm[key] !== "string") {
|
||||
return {
|
||||
status: 422,
|
||||
ref: "Odmdb",
|
||||
msg: "imageinvalid",
|
||||
data: { field: key },
|
||||
};
|
||||
}
|
||||
const match = itm[key].match(allowed);
|
||||
if (!match) {
|
||||
return {
|
||||
status: 422,
|
||||
ref: "Odmdb",
|
||||
msg: "imageinvalid",
|
||||
data: { field: key },
|
||||
};
|
||||
}
|
||||
const newk = key.slice("imgbase64_".length);
|
||||
if (!schema.properties[newk]) {
|
||||
return {
|
||||
status: 422,
|
||||
ref: "Odmdb",
|
||||
msg: "imgpropertiesmissing",
|
||||
data: { field: key, propertie: newk },
|
||||
};
|
||||
}
|
||||
const buffer = Buffer.from(match[2].replace(/\s+/g, ""), "base64");
|
||||
if (buffer.length == 0 || buffer.length > 5 * 1024 * 1024) {
|
||||
return {
|
||||
status: 422,
|
||||
ref: "Odmdb",
|
||||
msg: "imageinvalid",
|
||||
data: { field: key, size: buffer.length },
|
||||
};
|
||||
}
|
||||
if (!validmagic(buffer)) {
|
||||
return {
|
||||
status: 422,
|
||||
ref: "Odmdb",
|
||||
msg: "imageinvalid",
|
||||
data: { field: key },
|
||||
};
|
||||
}
|
||||
let webp;
|
||||
try {
|
||||
webp = await sharp(buffer, { limitInputPixels: 25000000 })
|
||||
.rotate()
|
||||
.webp({ quality: 80 })
|
||||
.toBuffer();
|
||||
} catch (err) {
|
||||
return {
|
||||
status: 422,
|
||||
ref: "Odmdb",
|
||||
msg: "imageinvalid",
|
||||
data: { field: key, error: err.message },
|
||||
};
|
||||
}
|
||||
const safek = String(newk)
|
||||
.replace(/[^A-Za-z0-9_-]/g, "_")
|
||||
.slice(0, 64);
|
||||
const filename = `apximg_${safeid}_${safek}.webp`;
|
||||
const finalpath = path.resolve(imgdir, filename);
|
||||
if (!finalpath.startsWith(`${imgdir}${path.sep}`)) {
|
||||
return {
|
||||
status: 422,
|
||||
ref: "Odmdb",
|
||||
msg: "imagepathinvalid",
|
||||
data: { field: key },
|
||||
};
|
||||
}
|
||||
writes.push({ key, newk, filename, webp, finalpath });
|
||||
}
|
||||
fs.ensureDirSync(imgdir);
|
||||
writes.forEach((write) => {
|
||||
fs.outputFileSync(write.finalpath, write.webp);
|
||||
itm[write.newk] = `objectimg/${objectname}/img/${write.filename}`;
|
||||
delete itm[write.key];
|
||||
});
|
||||
return {
|
||||
status: 200,
|
||||
ref: "Odmdb",
|
||||
msg: "imagesprepared",
|
||||
data: { written: writes.map((write) => write.finalpath) },
|
||||
};
|
||||
};
|
||||
|
||||
Odmdb.cud = (objectPathname, crud, itm, role, runindex = true) => {
|
||||
const getschema = Odmdb.Schema(objectPathname, true);
|
||||
if (getschema.status != 200) return getschema;
|
||||
@@ -637,6 +776,17 @@ Odmdb.cud = (objectPathname, crud, itm, role, runindex = true) => {
|
||||
data: { missingkey: getschema.data.schema.apxid },
|
||||
};
|
||||
}
|
||||
const pendingimages = Object.keys(itm).filter(
|
||||
(key) => key.startsWith("imgbase64_") && itm[key] !== ""
|
||||
);
|
||||
if (pendingimages.length > 0) {
|
||||
return {
|
||||
status: 422,
|
||||
ref: "Odmdb",
|
||||
msg: "imgbase64requirespreprocessing",
|
||||
data: { fields: pendingimages },
|
||||
};
|
||||
}
|
||||
fs.ensureDirSync(`${objectPathname}/itm/`);
|
||||
fs.ensureDirSync(`${objectPathname}/idx/`);
|
||||
const existid = fs.existsSync(
|
||||
@@ -774,40 +924,6 @@ Odmdb.cud = (objectPathname, crud, itm, role, runindex = true) => {
|
||||
//console.log(k, itmtostore[k], itmtostore[k] === "");
|
||||
if (itmtostore[k] === "") delete itmtostore[k];
|
||||
});
|
||||
Object.keys(itmtostore).forEach((k) => {
|
||||
//Manage base64 case image to convert ans store in webp
|
||||
if (k.includes("imgbase64_") && itmtostore[k] != "") {
|
||||
fs.ensureDirSync(`${objectPathname}/img/`);
|
||||
//console.log("check this for k:", k, " itmtostore[k]:", itmtostore[k]);
|
||||
const imgb64 = itmtostore[k].replace(
|
||||
/^data:image\/(png|png|gif|bmp|jpg|jpeg);base64,/,
|
||||
""
|
||||
); //.replace(/\+/g," ");
|
||||
const extension = itmtostore[k].split(";base64,")[0].split("/")[1];
|
||||
const newk = k.split("_").slice(1).join("_");
|
||||
const filenameimg = `/img/${
|
||||
itmtostore[getschema.data.schema.apxid]
|
||||
}_${newk}.${extension}`;
|
||||
if (!getschema.data.schema.properties[newk]) {
|
||||
if (log)
|
||||
console.log(
|
||||
currentmod,
|
||||
" Check your schema it miss a propertie because imgbase64_blabla must have a blabla propertie to host the pathfile image"
|
||||
);
|
||||
} else {
|
||||
const objectname = objectPathname.split("/").slice(-1)[0];
|
||||
itmtostore[newk] = `objectimg/${objectname}${filenameimg}`;
|
||||
//const webp = require("webp-converter");
|
||||
//const buf = Buffer.from(itmtostore[k], 'base64');
|
||||
//const imgwebp = await webp.bufferToWebp(buf);
|
||||
//@TODO use sharp to resize img with attribut sizeHW in propertie
|
||||
fs.writeFileSync(`${objectPathname}/${filenameimg}`, imgb64, {
|
||||
encoding: "base64",
|
||||
});
|
||||
delete itmtostore[k];
|
||||
}
|
||||
}
|
||||
});
|
||||
// check consistency of datatostore
|
||||
//console.log("itmstostore::::::", itmtostore);
|
||||
const chkdata = Checkjson.schema.data(
|
||||
@@ -859,6 +975,17 @@ Odmdb.cud = (objectPathname, crud, itm, role, runindex = true) => {
|
||||
*/
|
||||
Odmdb.runidx = (objectPathname, schema) => {
|
||||
const starttime=process.hrtime.bigint();
|
||||
if (
|
||||
!fs.existsSync(objectPathname) ||
|
||||
!fs.lstatSync(objectPathname).isDirectory()
|
||||
) {
|
||||
return {
|
||||
status: 404,
|
||||
ref: "Odmdb",
|
||||
msg: "objectdoesnotexist",
|
||||
data: { objectPathname },
|
||||
};
|
||||
}
|
||||
if (!schema || !schema.apxid) {
|
||||
const getschema = Odmdb.Schema(objectPathname, true);
|
||||
if (getschema.status != 200) {
|
||||
@@ -867,6 +994,14 @@ Odmdb.runidx = (objectPathname, schema) => {
|
||||
}
|
||||
schema = getschema.data.schema;
|
||||
}
|
||||
if (!Array.isArray(schema.apxidx)) {
|
||||
return {
|
||||
status: 406,
|
||||
ref: "Odmdb",
|
||||
msg: "apxidxmissing",
|
||||
data: { objectPathname },
|
||||
};
|
||||
}
|
||||
const ventil = {};
|
||||
schema.apxidx.forEach((id) => {
|
||||
ventil[id.name] = id;
|
||||
@@ -881,7 +1016,7 @@ Odmdb.runidx = (objectPathname, schema) => {
|
||||
} else {
|
||||
if (!schema.properties[id.keyval]){
|
||||
console.log(`Check ${objectPathname} index ${id.name} property ${id.keyval} does not exist`)
|
||||
process.exit(0)
|
||||
return
|
||||
}
|
||||
ventil[id.name].isunique = schema.apxuniquekey.includes(id.keyval);
|
||||
ventil[id.name].nbobjkey = id.objkey ? id.objkey.length : 0;
|
||||
|
||||
@@ -84,6 +84,20 @@ Pagans.keyrecovery = async (emailalias, tribe, search, lg) => {
|
||||
data: { emailalias, tribe, search, lg },
|
||||
};
|
||||
}
|
||||
const searchvalid =
|
||||
emailalias === "alias"
|
||||
? /^[a-z0-9]{3,64}$/.test(String(search))
|
||||
: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(
|
||||
String(search),
|
||||
);
|
||||
if (!/^[a-z0-9_-]{1,64}$/.test(String(tribe)) || !searchvalid) {
|
||||
return {
|
||||
status: 400,
|
||||
ref: "Pagans",
|
||||
msg: "searchinvalid",
|
||||
data: { emailalias, tribe, search },
|
||||
};
|
||||
}
|
||||
let emaillist = [];
|
||||
if (emailalias == "email") {
|
||||
search = search.toLowerCase();
|
||||
@@ -148,6 +162,14 @@ Pagans.keyrecovery = async (emailalias, tribe, search, lg) => {
|
||||
*
|
||||
**/
|
||||
Pagans.getalias = (alias) => {
|
||||
if (!/^[a-z0-9]{3,64}$/.test(String(alias))) {
|
||||
return {
|
||||
status: 400,
|
||||
ref: "Pagans",
|
||||
msg: "aliasinvalid",
|
||||
data: { alias },
|
||||
};
|
||||
}
|
||||
//bypass Odmdb cause all is public save ressources
|
||||
console.log(path.resolve(`../adminapi/objects/pagans/itm/${alias}.json`));
|
||||
if (fs.existsSync(`../adminapi/objects/pagans/itm/${alias}.json`)) {
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
const crypto = require("crypto");
|
||||
const conf = require(`../../../adminapi/objects/tribes/itm/adminapi.json`);
|
||||
|
||||
const Security = {};
|
||||
|
||||
Security.freeTextFields = [
|
||||
"biography",
|
||||
"title",
|
||||
"description",
|
||||
"message",
|
||||
"html",
|
||||
"text",
|
||||
"content",
|
||||
"comment",
|
||||
"subject",
|
||||
];
|
||||
|
||||
Security.hash = (value) =>
|
||||
crypto
|
||||
.createHash("sha256")
|
||||
.update(String(value === undefined ? "" : value))
|
||||
.digest("hex")
|
||||
.slice(0, 16);
|
||||
|
||||
Security.normalize = (value) => {
|
||||
let normalized = String(value);
|
||||
for (let i = 0; i < 3; i++) {
|
||||
try {
|
||||
const decoded = decodeURIComponent(normalized);
|
||||
if (decoded === normalized) break;
|
||||
normalized = decoded;
|
||||
} catch (err) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return normalized.replace(/\\/g, "/");
|
||||
};
|
||||
|
||||
Security.isTraversal = (value) => {
|
||||
if (typeof value !== "string" || value === "") return false;
|
||||
const normalized = Security.normalize(value);
|
||||
if (normalized.includes("\0")) return true;
|
||||
return normalized
|
||||
.split("/")
|
||||
.some((segment) => segment === ".." || segment === "..;");
|
||||
};
|
||||
|
||||
Security.redact = (value, depth = 0) => {
|
||||
if (depth > 6) return "[depthlimit]";
|
||||
if (value === null || value === undefined) return value;
|
||||
if (typeof value === "string") {
|
||||
return value.length > 2000 ? `${value.slice(0, 2000)}...[truncated]` : value;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.slice(0, 50).map((item) => Security.redact(item, depth + 1));
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const redacted = {};
|
||||
Object.keys(value)
|
||||
.slice(0, 100)
|
||||
.forEach((key) => {
|
||||
if (/privatekey|passphrase|xhash|password|secret|token|apikey|smtp|brevo/i.test(key)) {
|
||||
redacted[key] = "[redacted]";
|
||||
} else if (/^imgbase64_/i.test(key)) {
|
||||
redacted[key] = "[image]";
|
||||
} else {
|
||||
redacted[key] = Security.redact(value[key], depth + 1);
|
||||
}
|
||||
});
|
||||
return redacted;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
Security.throttlekey = (ip, field, value) =>
|
||||
`${ip}|${field}|${Security.hash(value)}`;
|
||||
|
||||
Security.throttled = new Map();
|
||||
Security.emailtimes = [];
|
||||
|
||||
Security.alert = (req, details = {}) => {
|
||||
const header = (req.session && req.session.header) || {};
|
||||
const ip =
|
||||
req.ip || (req.socket && req.socket.remoteAddress) || "unknown";
|
||||
const field = details.field || "unknown";
|
||||
const value =
|
||||
typeof details.value === "string"
|
||||
? details.value.slice(0, 500)
|
||||
: details.value;
|
||||
const now = Date.now();
|
||||
const throttlekey = Security.throttlekey(ip, field, value);
|
||||
const last = Security.throttled.get(throttlekey) || 0;
|
||||
if (now - last < 10 * 60 * 1000) return;
|
||||
Security.throttled.set(throttlekey, now);
|
||||
if (Security.throttled.size > 5000) {
|
||||
Security.throttled.forEach((ts, key) => {
|
||||
if (now - ts > 10 * 60 * 1000) Security.throttled.delete(key);
|
||||
});
|
||||
}
|
||||
|
||||
const entry = {
|
||||
at: new Date(now).toISOString(),
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
ip,
|
||||
xtribe: header.xtribe,
|
||||
xalias: header.xalias,
|
||||
field,
|
||||
value,
|
||||
body: Security.redact(req.body),
|
||||
};
|
||||
console.log("ALERT security", JSON.stringify(entry));
|
||||
|
||||
const emailalertsecu =
|
||||
conf && typeof conf.emailalertsecu === "string"
|
||||
? conf.emailalertsecu.trim()
|
||||
: "";
|
||||
if (!emailalertsecu) return;
|
||||
Security.emailtimes = Security.emailtimes.filter(
|
||||
(ts) => now - ts < 60 * 60 * 1000
|
||||
);
|
||||
if (Security.emailtimes.length >= 30) return;
|
||||
Security.emailtimes.push(now);
|
||||
|
||||
const Notifications = require("./Notifications.js");
|
||||
Notifications.sendmail(
|
||||
{
|
||||
to: emailalertsecu,
|
||||
subject: `[apXtri security] ${field} - ${req.method} ${req.originalUrl}`,
|
||||
text: JSON.stringify(entry, null, 2),
|
||||
html: `<pre>${JSON.stringify(entry, null, 2)}</pre>`,
|
||||
},
|
||||
"adminapi"
|
||||
).catch((err) => {
|
||||
console.log("ALERT security email failed", err.message);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Security;
|
||||
@@ -16,6 +16,24 @@ const log = process.env.ACTIVELOG ? process.env.ACTIVELOG.split(',').includes(cu
|
||||
const Wwws = {};
|
||||
|
||||
Wwws.getwco = (wconame, ctx) => {
|
||||
const safename = (value) =>
|
||||
typeof value === "string" && /^[A-Za-z0-9_.:-]{1,64}$/.test(value);
|
||||
const tagids = ctx.tagid ? String(ctx.tagid).split(",") : [];
|
||||
if (
|
||||
!safename(wconame) ||
|
||||
!safename(ctx.tribe) ||
|
||||
!safename(ctx.wcotribe) ||
|
||||
!safename(ctx.xapp) ||
|
||||
!safename(ctx.pagename) ||
|
||||
tagids.some((tagid) => !safename(tagid))
|
||||
) {
|
||||
return {
|
||||
status: 400,
|
||||
ref: "Wwws",
|
||||
msg: "queryinvalid",
|
||||
data: { wconame, query: ctx },
|
||||
};
|
||||
}
|
||||
/**
|
||||
* In a webpage to add a wco
|
||||
* <script src="/api/adminapi/wwws/getwco/wco.js?wcotribe=adminapi&tribe=adminapi&xapp=admin&pagename=apxid&code=enjoy&tagid=id"></script>
|
||||
@@ -486,6 +504,22 @@ Wwws.initlocalwco = (tribwco, profils, lg) => {
|
||||
};
|
||||
|
||||
Wwws.initlocaldata = (tribe, appname, pagename, version, profils, lg) => {
|
||||
const safename = (value) =>
|
||||
typeof value === "string" && /^[A-Za-z0-9_.:-]{1,64}$/.test(value);
|
||||
if (
|
||||
!safename(tribe) ||
|
||||
!safename(appname) ||
|
||||
!safename(pagename) ||
|
||||
!/^[A-Za-z]{2}$/.test(String(lg)) ||
|
||||
!/^\d{1,16}$/.test(String(version))
|
||||
) {
|
||||
return {
|
||||
status: 400,
|
||||
ref: "Wwws",
|
||||
msg: "paraminvalid",
|
||||
data: { tribe, appname, pagename, version, lg },
|
||||
};
|
||||
}
|
||||
const fileparam = `../${tribe}/objects/wwws/itm/${appname}.json`;
|
||||
//console.log(path.resolve(fileparam));
|
||||
if (!fs.existsSync(fileparam)) {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
const express = require("express");
|
||||
const crypto = require("crypto");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
// Classes
|
||||
const Notifications = require("../models/Notifications.js");
|
||||
|
||||
const emailpattern =
|
||||
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
|
||||
// Middlewares
|
||||
const checkHeaders = require("../middlewares/checkHeaders");
|
||||
const isAuthenticated = require("../middlewares/isAuthenticated");
|
||||
@@ -26,7 +30,7 @@ const router = express.Router();
|
||||
**/
|
||||
router.get("/messages/:alias/:tribeId", (req, res) => {
|
||||
const getnot = Notifications.get(req.params.alias, req.params.tribeId);
|
||||
res.status(getalias.status).send(getalias);
|
||||
res.status(getnot.status).json(getnot);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -119,6 +123,34 @@ router.post("/registeranonymous", checkHeaders, (req, res) => {
|
||||
data: { typekey: req.body.typekey },
|
||||
});
|
||||
}
|
||||
const allowedtribes = [
|
||||
"adminapi",
|
||||
...(Array.isArray(req.app.locals.tribeids) ? req.app.locals.tribeids : []),
|
||||
];
|
||||
if (!allowedtribes.includes(req.body.tribe)) {
|
||||
return res.status(404).json({
|
||||
status: 404,
|
||||
ref: "Notifications",
|
||||
msg: "tribeiddoesnotexist",
|
||||
data: { tribe: req.body.tribe },
|
||||
});
|
||||
}
|
||||
if (!/^[A-Za-z0-9_.-]{1,64}$/.test(req.body.mlist || "")) {
|
||||
return res.status(400).json({
|
||||
status: 400,
|
||||
ref: "Notifications",
|
||||
msg: "mlistinvalid",
|
||||
data: { mlist: req.body.mlist },
|
||||
});
|
||||
}
|
||||
if (!/^[A-Za-z0-9_.-]{1,64}$/.test(req.body.srckey || "")) {
|
||||
return res.status(400).json({
|
||||
status: 400,
|
||||
ref: "Notifications",
|
||||
msg: "srckeyinvalid",
|
||||
data: { srckey: req.body.srckey },
|
||||
});
|
||||
}
|
||||
const key = req.body.contactpoint
|
||||
? req.body.contactpoint
|
||||
: req.body[req.body.typekey];
|
||||
@@ -201,7 +233,18 @@ router.get(
|
||||
*
|
||||
*/
|
||||
router.get("/stat/maillinglst/:tribe", checkHeaders, async (req, res) => {
|
||||
console.log("passe là");
|
||||
const allowedtribes = [
|
||||
"adminapi",
|
||||
...(Array.isArray(req.app.locals.tribeids) ? req.app.locals.tribeids : []),
|
||||
];
|
||||
if (!allowedtribes.includes(req.params.tribe)) {
|
||||
return res.status(404).json({
|
||||
status: 404,
|
||||
ref: "Notifications",
|
||||
msg: "tribeiddoesnotexist",
|
||||
data: { tribe: req.params.tribe },
|
||||
});
|
||||
}
|
||||
const resstat = Notifications.statmaillist(req.params.tribe);
|
||||
res.status(resstat.status).json(resstat);
|
||||
});
|
||||
@@ -270,8 +313,19 @@ router.get("/contact", checkHeaders, isAuthenticated, (req, res) => {
|
||||
*/
|
||||
router.get("/checkemail/:email/:digit6", checkHeaders, async (req, res) => {
|
||||
const { email, digit6 } = req.params;
|
||||
if (!emailpattern.test(email)) {
|
||||
return res.status(400).json({
|
||||
status: 400,
|
||||
ref: "Notifications",
|
||||
msg: "emailinvalid",
|
||||
data: { email },
|
||||
});
|
||||
}
|
||||
const tmpDir = path.join(__dirname, "../../tmp");
|
||||
const tmpFile = path.join(tmpDir, email);
|
||||
const tmpFile = path.join(
|
||||
tmpDir,
|
||||
`${crypto.createHash("sha256").update(email).digest("hex")}.txt`
|
||||
);
|
||||
|
||||
if (digit6 === "0") {
|
||||
const code = email.includes("smatchit.io") ? "789000" : Math.floor(100000 + Math.random() * 900000).toString();
|
||||
|
||||
+24
-4
@@ -82,7 +82,7 @@ router.get(
|
||||
checkHeaders,
|
||||
isAuthenticated,
|
||||
(req, res) => {
|
||||
const objectPathname = `../${req.params.tribe}/objects/options/${req.params.optionname}_${req.header.xlang}.json`;
|
||||
const objectPathname = `../${req.params.tribe}/objects/options/${req.params.optionname}_${req.session.header.xlang}.json`;
|
||||
if (fs.existsSync(objectPathname)) {
|
||||
res.status(200).json({
|
||||
status: 200,
|
||||
@@ -293,14 +293,24 @@ router.post(
|
||||
"/itm/:tribe/:objectname",
|
||||
checkHeaders,
|
||||
isAuthenticated,
|
||||
(req, res) => {
|
||||
async (req, res) => {
|
||||
// Create an item of an object with no specificities
|
||||
// if specificities then create a route / model that import odmdb
|
||||
const objectPathname = `../${req.params.tribe}/objects/${req.params.objectname}`;
|
||||
const prepared = await Odmdb.prepareUploadedImages(
|
||||
objectPathname,
|
||||
req.body
|
||||
);
|
||||
if (prepared.status != 200) {
|
||||
return res.status(prepared.status).json(prepared);
|
||||
}
|
||||
const postitm = Odmdb.cud(objectPathname, "C", req.body, {
|
||||
xprofils: req.session.header.xprofils,
|
||||
xalias: req.session.header.xalias,
|
||||
});
|
||||
if (postitm.status != 200 && prepared.data.written.length > 0) {
|
||||
prepared.data.written.forEach((file) => fs.removeSync(file));
|
||||
}
|
||||
res.status(postitm.status).json(postitm);
|
||||
}
|
||||
);
|
||||
@@ -336,14 +346,24 @@ router.put(
|
||||
"/itm/:tribe/:objectname",
|
||||
checkHeaders,
|
||||
isAuthenticated,
|
||||
(req, res) => {
|
||||
async (req, res) => {
|
||||
// Create an item of an object with no specificities
|
||||
// if specificities then create a route / model that import odmdb
|
||||
const objectPathname = `../${req.params.tribe}/objects/${req.params.objectname}`;
|
||||
const prepared = await Odmdb.prepareUploadedImages(
|
||||
objectPathname,
|
||||
req.body
|
||||
);
|
||||
if (prepared.status != 200) {
|
||||
return res.status(prepared.status).json(prepared);
|
||||
}
|
||||
const postitm = Odmdb.cud(objectPathname, "U", req.body, {
|
||||
xprofils: req.session.header.xprofils,
|
||||
xalias: req.session.header.xalias,
|
||||
});
|
||||
if (postitm.status != 200 && prepared.data.written.length > 0) {
|
||||
prepared.data.written.forEach((file) => fs.removeSync(file));
|
||||
}
|
||||
res.status(postitm.status).json(postitm);
|
||||
}
|
||||
);
|
||||
@@ -761,7 +781,7 @@ router.put(
|
||||
isAuthenticated,
|
||||
(req, res) => {
|
||||
const fullpath = path.resolve(
|
||||
`${__dirname}/tribes/${req.session.header.xworkon}/schema/${req.params.pathobjectname}.json`
|
||||
`${__dirname}/tribes/${req.session.header.xworkon}/schema/${req.params.objectname}.json`
|
||||
);
|
||||
const set = Odmdb.setObject(
|
||||
path.resolve(`${__dirname}/tribes/${req.session.header.xworkon}`)
|
||||
|
||||
+86
-1
@@ -142,6 +142,31 @@ router.post("/", checkHeaders, isAuthenticated, async (req, res) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!/^[a-z0-9]{3,64}$/.test(req.body.alias || "") ||
|
||||
req.body.alias !== req.session.header.xalias
|
||||
) {
|
||||
res.status(400).json({
|
||||
status: 400,
|
||||
ref: "Pagans",
|
||||
msg: "aliasinvalid",
|
||||
data: { alias: req.body.alias, xalias: req.session.header.xalias },
|
||||
});
|
||||
return;
|
||||
}
|
||||
const allowedtribes = [
|
||||
"adminapi",
|
||||
...(Array.isArray(req.app.locals.tribeids) ? req.app.locals.tribeids : []),
|
||||
];
|
||||
if (!allowedtribes.includes(req.body.trustedtribe)) {
|
||||
res.status(404).json({
|
||||
status: 404,
|
||||
ref: "Pagans",
|
||||
msg: "tribeiddoesnotexist",
|
||||
data: { tribe: req.body.trustedtribe },
|
||||
});
|
||||
return;
|
||||
}
|
||||
const objpagan = { alias: req.body.alias, publickey: req.body.publickey };
|
||||
console.log(path.resolve(`../adminapi/objects/pagans`));
|
||||
const newpagan = Odmdb.cud(`../adminapi/objects/pagans`, "C", objpagan, role);
|
||||
@@ -219,6 +244,14 @@ router.post("/", checkHeaders, isAuthenticated, async (req, res) => {
|
||||
* Delete an alias and his publickey, this mean that publickey disapear as well as alias. We set dt_delete
|
||||
* */
|
||||
router.delete("/alias/:alias", checkHeaders, isAuthenticated, (req, res) => {
|
||||
if (req.params.alias !== req.session.header.xalias) {
|
||||
return res.status(403).json({
|
||||
status: 403,
|
||||
ref: "Pagans",
|
||||
msg: "profilnotallow",
|
||||
data: { alias: req.params.alias, xalias: req.session.header.xalias },
|
||||
});
|
||||
}
|
||||
const personpath = `../adminapi/objects/pagans`;
|
||||
const role = {
|
||||
xalias: req.session.header.xalias,
|
||||
@@ -263,6 +296,17 @@ router.delete(
|
||||
checkHeaders,
|
||||
isAuthenticated,
|
||||
(req, res) => {
|
||||
const privileged =
|
||||
req.session.header.xprofils.includes("druid") ||
|
||||
req.session.header.xprofils.includes("mayor");
|
||||
if (req.params.alias !== req.session.header.xalias && !privileged) {
|
||||
return res.status(403).json({
|
||||
status: 403,
|
||||
ref: "Pagans",
|
||||
msg: "profilnotallow",
|
||||
data: { alias: req.params.alias, xalias: req.session.header.xalias },
|
||||
});
|
||||
}
|
||||
const personpath = `../${req.params.tribe}/objects/persons`;
|
||||
const role = {
|
||||
xalias: req.session.header.xalias,
|
||||
@@ -331,8 +375,30 @@ router.get("/person/:alias", checkHeaders, isAuthenticated, (req, res) => {
|
||||
* @apiBody {string} [addprofils] must be alone no other Body field
|
||||
* @apiBody {string} [removeprofils] must be alone
|
||||
*/
|
||||
router.put("/person/:tribe", checkHeaders, isAuthenticated, (req, res) => {
|
||||
router.put("/person/:tribe", checkHeaders, isAuthenticated, async (req, res) => {
|
||||
//console.log(req.body);
|
||||
if (!req.body || !/^[a-z0-9]{3,64}$/.test(req.body.alias || "")) {
|
||||
return res.status(400).json({
|
||||
status: 400,
|
||||
ref: "Pagans",
|
||||
msg: "aliasinvalid",
|
||||
data: { alias: req.body && req.body.alias },
|
||||
});
|
||||
}
|
||||
const privileged =
|
||||
req.session.header.xprofils.includes("druid") ||
|
||||
req.session.header.xprofils.includes("mayor");
|
||||
if (
|
||||
req.body.alias !== req.session.header.xalias &&
|
||||
!privileged
|
||||
) {
|
||||
return res.status(403).json({
|
||||
status: 403,
|
||||
ref: "Pagans",
|
||||
msg: "profilnotallow",
|
||||
data: { alias: req.body.alias, xalias: req.session.header.xalias },
|
||||
});
|
||||
}
|
||||
const pathobj = `../${req.params.tribe}/objects/persons`;
|
||||
const action = fs.existsSync(`${pathobj}/itm/${req.body.alias}.json`)
|
||||
? "U"
|
||||
@@ -386,10 +452,17 @@ router.put("/person/:tribe", checkHeaders, isAuthenticated, (req, res) => {
|
||||
if (!req.body.profils) {
|
||||
req.body.profils = ["anonymous", "pagans", "persons"];
|
||||
}
|
||||
const prepared = await Odmdb.prepareUploadedImages(pathobj, req.body);
|
||||
if (prepared.status != 200) {
|
||||
return res.status(prepared.status).json(prepared);
|
||||
}
|
||||
const personup = Odmdb.cud(pathobj, action, req.body, {
|
||||
xprofils: req.session.header.xprofils,
|
||||
xalias: req.session.header.xalias,
|
||||
});
|
||||
if (personup.status != 200 && prepared.data.written.length > 0) {
|
||||
prepared.data.written.forEach((file) => fs.removeSync(file));
|
||||
}
|
||||
if (log) console.log(currentmod, " personupdate or create:", personup);
|
||||
res.status(personup.status).json(personup);
|
||||
}
|
||||
@@ -415,6 +488,18 @@ router.put("/person/:tribe", checkHeaders, isAuthenticated, (req, res) => {
|
||||
*
|
||||
*/
|
||||
router.post("/keyrecovery", checkHeaders, async (req, res) => {
|
||||
const allowedtribes = [
|
||||
"adminapi",
|
||||
...(Array.isArray(req.app.locals.tribeids) ? req.app.locals.tribeids : []),
|
||||
];
|
||||
if (!allowedtribes.includes(req.body.tribe)) {
|
||||
return res.status(404).json({
|
||||
status: 404,
|
||||
ref: "Pagans",
|
||||
msg: "tribeiddoesnotexist",
|
||||
data: { tribe: req.body.tribe },
|
||||
});
|
||||
}
|
||||
try {
|
||||
const recores = await Pagans.keyrecovery(
|
||||
req.body.emailalias,
|
||||
|
||||
@@ -33,6 +33,27 @@ const router = express.Router();
|
||||
*/
|
||||
router.post("/newdevice", checkHeaders, async (req, res) => {
|
||||
if (log) console.log(currentmod, "post new device with", req.body);
|
||||
const xuuid = req.session.header.xuuid;
|
||||
if (
|
||||
!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(
|
||||
xuuid
|
||||
)
|
||||
) {
|
||||
return res.status(400).json({
|
||||
status: 400,
|
||||
ref: "Trackings",
|
||||
msg: "xuuidinvalid",
|
||||
data: { xuuid },
|
||||
});
|
||||
}
|
||||
if (!req.body || typeof req.body !== "object" || Array.isArray(req.body)) {
|
||||
return res.status(400).json({
|
||||
status: 400,
|
||||
ref: "Trackings",
|
||||
msg: "bodyinvalid",
|
||||
data: {},
|
||||
});
|
||||
}
|
||||
const newdevice = { status: 200, ref: "Trackings", msg: "success", data: {} };
|
||||
const role = {
|
||||
xalias: req.session.header.xalias,
|
||||
@@ -57,9 +78,9 @@ router.post("/newdevice", checkHeaders, async (req, res) => {
|
||||
confdevice
|
||||
);
|
||||
}
|
||||
const devicepath = `../${req.session.header.xtribe}/objects/devices/itm/${req.session.header.xuuid}.json`;
|
||||
const devicepath = `../${req.session.header.xtribe}/objects/devices/itm/${xuuid}.json`;
|
||||
const device = req.body;
|
||||
device.xuuid = req.session.header.xuuid;
|
||||
device.xuuid = xuuid;
|
||||
if (req.session.header.xalias != "anonymous") {
|
||||
device.alias = req.session.header.xalias;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,32 @@ const checkHeaders = require( '../middlewares/checkHeaders' );
|
||||
const isAuthenticated = require( '../middlewares/isAuthenticated' );
|
||||
const router = express.Router();
|
||||
|
||||
const disabledroutes = [
|
||||
'/config',
|
||||
'/www',
|
||||
'/actionanonyme',
|
||||
'/action',
|
||||
'/clientconf',
|
||||
'/spaceweb',
|
||||
'/plugins',
|
||||
'/dirls',
|
||||
'/ls',
|
||||
'/sendjson',
|
||||
'/downloadls',
|
||||
'/upfilepond',
|
||||
'/file',
|
||||
'/uploadfile',
|
||||
'/uploadzip',
|
||||
'/upload',
|
||||
];
|
||||
router.use( ( req, res, next ) => {
|
||||
if( disabledroutes.some( p => req.path === p || req.path.startsWith( `${p}/` ) ) ) {
|
||||
return res.status( 410 )
|
||||
.json( { status: 410, ref: 'Tribes', msg: 'routedisabled' } );
|
||||
}
|
||||
next();
|
||||
} );
|
||||
|
||||
|
||||
/**
|
||||
* @api {get} adminapi/tribes/conf/:tribe - tribe list
|
||||
|
||||
+23
-1
@@ -53,10 +53,32 @@ router.get("/getwco/:wconame", (req, res) => {
|
||||
missquery+=q+" "
|
||||
}
|
||||
})
|
||||
const namepattern = /^[A-Za-z0-9_.:-]{1,64}$/;
|
||||
const allowedtribes = [
|
||||
"adminapi",
|
||||
...(Array.isArray(req.app.locals.tribeids) ? req.app.locals.tribeids : []),
|
||||
];
|
||||
const wconame = req.params.wconame.replace(/\.js$/, "");
|
||||
const tagids = req.query.tagid ? String(req.query.tagid).split(",") : [];
|
||||
const invalid =
|
||||
!namepattern.test(wconame) ||
|
||||
!allowedtribes.includes(req.query.tribe) ||
|
||||
!allowedtribes.includes(req.query.wcotribe) ||
|
||||
!namepattern.test(req.query.xapp || "") ||
|
||||
!namepattern.test(req.query.pagename || "") ||
|
||||
String(req.query.code || "").length > 128 ||
|
||||
tagids.some((tagid) => !namepattern.test(tagid));
|
||||
if (missquery!=""){
|
||||
res.status(403).json({status:403,ref:"Wwws",msg:"missquery",data:{missquery}})
|
||||
}else if (invalid) {
|
||||
res.status(400).json({
|
||||
status: 400,
|
||||
ref: "Wwws",
|
||||
msg: "queryinvalid",
|
||||
data: { wconame, query: req.query },
|
||||
});
|
||||
}else{
|
||||
const getwco=Wwws.getwco(req.params.wconame.replace(/\.js$/,''),req.query)
|
||||
const getwco=Wwws.getwco(wconame,req.query)
|
||||
if (getwco.status==200){
|
||||
res.set('Content-Type', 'application/javascript');
|
||||
res.sendFile(getwco.data.file)
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const glob = require("glob");
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const deletefiles = args.includes("--delete");
|
||||
const quarantine = args.includes("--quarantine");
|
||||
const dryrun = !deletefiles && !quarantine;
|
||||
const adminapiroot = path.resolve(__dirname, "../..");
|
||||
const townroot = path.resolve(adminapiroot, "..");
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const quarantinedir = path.join(
|
||||
adminapiroot,
|
||||
"tmp",
|
||||
"quarantine",
|
||||
timestamp
|
||||
);
|
||||
const imageext = [".webp", ".png", ".jpg", ".jpeg", ".gif"];
|
||||
const imagelabel = /_(imgavatar|backgroundimage|companylogo)\./i;
|
||||
|
||||
const readhead = (file) => {
|
||||
const fd = fs.openSync(file, "r");
|
||||
const buffer = Buffer.alloc(12);
|
||||
fs.readSync(fd, buffer, 0, 12, 0);
|
||||
fs.closeSync(fd);
|
||||
return buffer;
|
||||
};
|
||||
|
||||
const validmagic = (buffer) => {
|
||||
if (buffer.length < 12) return false;
|
||||
const ispng =
|
||||
buffer[0] === 0x89 &&
|
||||
buffer[1] === 0x50 &&
|
||||
buffer[2] === 0x4e &&
|
||||
buffer[3] === 0x47 &&
|
||||
buffer[4] === 0x0d &&
|
||||
buffer[5] === 0x0a &&
|
||||
buffer[6] === 0x1a &&
|
||||
buffer[7] === 0x0a;
|
||||
const isjpeg =
|
||||
buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff;
|
||||
const gif = buffer.slice(0, 6).toString();
|
||||
const isgif = gif === "GIF87a" || gif === "GIF89a";
|
||||
const iswebp =
|
||||
buffer.slice(0, 4).toString() === "RIFF" &&
|
||||
buffer.slice(8, 12).toString() === "WEBP";
|
||||
return ispng || isjpeg || isgif || iswebp;
|
||||
};
|
||||
|
||||
const objectdirs = [];
|
||||
glob
|
||||
.sync(`${townroot}/*/objects`, { nodir: false, follow: false })
|
||||
.forEach((dir) => {
|
||||
if (!objectdirs.includes(dir)) objectdirs.push(dir);
|
||||
});
|
||||
const adminobjects = path.join(adminapiroot, "objects");
|
||||
if (fs.existsSync(adminobjects) && !objectdirs.includes(adminobjects)) {
|
||||
objectdirs.push(adminobjects);
|
||||
}
|
||||
|
||||
const flagged = [];
|
||||
objectdirs.forEach((objectdir) => {
|
||||
if (!fs.existsSync(objectdir)) return;
|
||||
glob
|
||||
.sync("**/*", { cwd: objectdir, nodir: true, dot: false, follow: false })
|
||||
.forEach((rel) => {
|
||||
if (/(^|\/)(node_modules|tmp|logs|\.git)(\/|$)/.test(rel)) return;
|
||||
const file = path.join(objectdir, rel);
|
||||
let stat;
|
||||
try {
|
||||
stat = fs.lstatSync(file);
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
if (!stat.isFile() || stat.isSymbolicLink()) return;
|
||||
const base = path.basename(file);
|
||||
const dirparts = rel.split(path.sep);
|
||||
const top = dirparts[0];
|
||||
const inobjectimg = dirparts.length >= 3 && dirparts[1] === "img";
|
||||
const ext = path.extname(base).toLowerCase();
|
||||
let reason = "";
|
||||
if (base.startsWith("zz_")) {
|
||||
reason = "zzartifact";
|
||||
} else if (["wwws", "apidoc"].includes(top)) {
|
||||
return;
|
||||
} else if (!inobjectimg && imagelabel.test(base)) {
|
||||
reason = "imageoutsideimg";
|
||||
} else if (inobjectimg && !imageext.includes(ext)) {
|
||||
reason = "extensionnotallowed";
|
||||
} else if (
|
||||
inobjectimg &&
|
||||
imageext.includes(ext) &&
|
||||
!validmagic(readhead(file))
|
||||
) {
|
||||
reason = "invalidmagic";
|
||||
}
|
||||
if (reason) {
|
||||
flagged.push({ file, reason, rel, objectdir });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (flagged.length == 0) {
|
||||
console.log("No suspicious image found.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!dryrun) fs.ensureDirSync(quarantinedir);
|
||||
|
||||
flagged.forEach((item) => {
|
||||
console.log(
|
||||
`${dryrun ? "WOULD QUARANTINE" : deletefiles ? "DELETE" : "QUARANTINE"} [${item.reason}] ${item.file}`
|
||||
);
|
||||
if (dryrun) return;
|
||||
if (deletefiles) {
|
||||
fs.removeSync(item.file);
|
||||
return;
|
||||
}
|
||||
const dest = path.resolve(
|
||||
quarantinedir,
|
||||
path.relative(townroot, item.file)
|
||||
);
|
||||
if (!dest.startsWith(`${quarantinedir}${path.sep}`)) {
|
||||
console.log(`skip unsafe quarantine path: ${dest}`);
|
||||
return;
|
||||
}
|
||||
fs.moveSync(item.file, dest, { overwrite: true });
|
||||
});
|
||||
|
||||
if (!dryrun) {
|
||||
fs.ensureDirSync(quarantinedir);
|
||||
fs.outputJSONSync(path.join(quarantinedir, "report.json"), flagged, {
|
||||
spaces: 2,
|
||||
});
|
||||
console.log(`Report written to ${path.join(quarantinedir, "report.json")}`);
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
"xtrkversion"
|
||||
],
|
||||
"appset": {
|
||||
"trust proxy": true
|
||||
"trust proxy": 1
|
||||
},
|
||||
"json": {
|
||||
"limit": "10mb",
|
||||
@@ -49,6 +49,7 @@
|
||||
"worker_connections": 1024
|
||||
},
|
||||
"emailcontact": "",
|
||||
"emailalertsecu": "",
|
||||
"smtp": {},
|
||||
"sms": {}
|
||||
}
|
||||
@@ -56,6 +56,8 @@ location /api/ {
|
||||
proxy_pass http://localhost:{{{api.port}}};
|
||||
proxy_redirect off;
|
||||
include proxy_params;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
location /socket/ {
|
||||
proxy_pass http://127.0.0.1:{{socket.port}};
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"title": "alias",
|
||||
"description": "A unique string identifying a unique public key",
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9]{3,64}$",
|
||||
"options": {
|
||||
"$ref": "adminapi/objects/pagans/idx/lst_alias"
|
||||
}
|
||||
@@ -17,6 +18,7 @@
|
||||
"title": "Owner of this person",
|
||||
"description": "For accessright purpose this is always equal as alias",
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9]{3,64}$",
|
||||
"options": {
|
||||
"$ref": "adminapi/objects/pagans/idx/lst_alias"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user