diff --git a/models/Notifications.js b/models/Notifications.js index 85031e9..97f3ef0 100644 --- a/models/Notifications.js +++ b/models/Notifications.js @@ -3,6 +3,7 @@ const path = require("path"); const fs = require("fs-extra"); const axios = require("axios"); const dayjs = require("dayjs"); +const Mustache = require('mustache'); const Checkjson = require(`./Checkjson.js`); //const smtp = require("smtp-client"); const nodemailer = require("nodemailer"); @@ -212,18 +213,27 @@ Notifications.manageemail = (data, template, tribe) => { * data optionaly can contain Cc,Bcc as array of emails and attachments as array of filename (into the server) * Await the 1st email */ - + //console.log(data) if (!data.emailsto || data.emailsto.length == 0) { return { status: 406, ref: "Notifications", - msg: "emailsmissing", + msg: "emailstomissing", data: data, }; } if (typeof data.emailsto === "string") data.emailsto = [data.emailsto]; + + if (!fs.existsSync(path.resolve(template))){ + return { + status: 404, + ref: "Notification", + msg: "templatenotfound", + data: { template:path.resolve(template) }, + } + } const tplemail = require(path.resolve(template)); - let sendit; + let sendit={status:200,ref:"Notifications",msg:"successfullsend"}; data.emailsto.forEach(async (e, i) => { if (Checkjson.testformat(e, "email")) { const dat = {}; @@ -233,16 +243,17 @@ Notifications.manageemail = (data, template, tribe) => { dat.text = Mustache.render(tplemail.text, data); dat.Cc = data.Cc ? tplemail.Cc.push(data.Cc) : tplemail.Cc; dat.Bcc = data.Bcc ? tplemail.Bcc.push(data.Bcc) : tplemail.Bcc; - dat.attachments = tplemail.attachments.push(data.attachments); - const sendit = Notifications.sendmail(data, tribe); + if (data.attachments){ + data.attachments.forEach(a=>tplemail.attachments.push(a)) + } if (i == 0) { - sendit = await Notifications.sendmail(data, tribe); - if (sendit.status != 200) return sendit; + sendit = await Notifications.sendmail(dat, tribe); + if (sendit.status != 200) return {status:406,ref:"Notifications",msg:"emailnotsent"}; } else { - Notifications.sendmail(data, tribe); + Notifications.sendmail(dat, tribe); } } else { - // not a well formated email + // not a well formated email @todo add a log file to follow it } }); return sendit; @@ -250,6 +261,17 @@ Notifications.manageemail = (data, template, tribe) => { Notifications.sendmail = async (data, tribe) => { /** + * * in conf global or in /itm/{tribe}.json must have valid parameter emailcontact must be authorized by the smtp + * "emailcontact": "noreply@smatchit.io", + * "smtp": { + * "host": "smtp-relay.brevo.com", + * "port": 587, + * "secure": false, + * "auth": { + * "user": "xx", + * "pass": "yy" + * } + * } * See https://nodemailer.com/message/ for available fields to add * @param {string} [data.from] an email authorized by smtp used priority from header xtribe * @param {string} data.to list of email separate by , @@ -265,7 +287,7 @@ Notifications.sendmail = async (data, tribe) => { * {filename:'img.png',path:"data:text/svg;base64.aGVsbG8gd29ybGQ="} * * @example data - * {"to":"wall-ants.ndda.fr", + * {"to":"wall-ants@ndda.fr", * "subject":"Test", * "html":"

test welcome

", * "text":"test welcome", @@ -305,13 +327,18 @@ Notifications.sendmail = async (data, tribe) => { const conftribfile = `../../itm/${tribe}.json`; if (fs.existsSync(conftribfile)) { const conftrib = fs.readJSONSync(conftribfile); + if (!conftrib.emailcontact){ + return { + status: 428, + ref: "Notifications", + msg: "missingemailcontactinconf", + data: { tribe }, + }; + } confsmtp = conftrib.smtp; - if (!data.from) data.from = conftrib.emailcontact; + if (!data.from || data.from == conf.emailcontact) data.from = conftrib.emailcontact; } - - //const client = smtp.connect(confsmtp); const transporter = await nodemailer.createTransport(confsmtp); - //@todo add attachments management if (data.filelist) { data.attachments = []; let missingfile = []; @@ -331,6 +358,7 @@ Notifications.sendmail = async (data, tribe) => { } //console.log("data:", data); const res = await transporter.sendMail(data); + //console.log(res) if ( res.accepted && data.to.split(",").reduce((acc, m) => acc && res.accepted.includes(m), true) diff --git a/models/PagansPeter.js b/models/PagansPeter.js new file mode 100644 index 0000000..b8d0fcd --- /dev/null +++ b/models/PagansPeter.js @@ -0,0 +1,221 @@ +const glob = require("glob"); +const path = require("path"); +const dayjs = require("dayjs"); +const fs = require("fs-extra"); +const axios = require("axios"); +const Mustache = require("mustache"); +const openpgp = require("openpgp"); +const Notifications = require("./Notifications.js"); +const Odmdb = require("./Odmdb.js"); + +const conf = require(`../../../conf.json`); +const currentmod = "Pagans"; +const log = conf.api.activelog.includes(currentmod); +/** + * Pagan Management numeric Identity and Person (Person = Pagan Id + tribe) + * + * + * + */ +const Pagans = {}; + +Pagans.create=()=>{ +//from router.post("/", checkHeaders, isAuthenticated, async (req, res) +} + +Pagans.joinpersontotribe=()=>{ +// from router.post("/", checkHeaders, isAuthenticated, async (req, res) + +} +/** + * Remove authentification token after a logout + * @param {string} alias + * @param {string} tribe + * @param {integer} xdays + * @param {string} xhash + * @returns {status:200, ref:"Pagans",msg:"logout"} + * tmpfs name file has to be on line with the tmpfs create by isAuthenticated + * tmpfs contain profils name for a tribe/ + */ +Pagans.logout = (alias, tribe, xdays, xhash) => { + //console.log(alias, tribe, xdays, xhash); + // inline with middleware isAuthenticated.js + let tmpfs = `../../tmp/tokens/${alias}_${tribe}_${xdays}`; + //max filename in ext4: 255 characters + tmpfs += `_${xhash.substring(150, 150 + tmpfs.length - 249)}.json`; + fs.remove(tmpfs); + if (log) console.log(currentmod, "logout token", tmpfs); + return { status: 200, ref: "Pagans", msg: "logout" }; +}; + +/** + * @param {string} alias a alias that exist or not + * @return {object} { status: 200, ref:"pagans",msg:"aliasexist",data: { alias, publicKey } } + * { status: 404, ref:"pagans",msg:"aliasdoesnotexist",data: { alias} } + * + **/ +Pagans.getalias = (alias) => { + //bypass Odmdb cause all is public save ressources + console.log(path.resolve(`../objects/pagans/itm/${alias}.json`)); + if (fs.existsSync(`../objects/pagans/itm/${alias}.json`)) { + return { + status: 200, + ref: "Pagans", + msg: "aliasexist", + data: fs.readJSONSync(`../objects/pagans/itm/${alias}.json`), + }; + } else { + return { + status: 404, + ref: "Pagans", + msg: "aliasdoesnotexist", + data: { alias }, + }; + } +}; + +/** + * Send email with alias's keys to email or person alias person.recovery.email + * + * If email or pubkey is undefined then get data from tribe/person(alias) + * Send email with keys + * @param {object} data + * @param {string} data.alias + * @param {pgpPrivate} [data.privatekey] + * @param {string} [data.passphrase] + * @param {string} data.tribe + * @param {pgpPublic} [data.publickey] + * @param {string} [data.email] + * @param {string} data.lg + */ +Pagans.sendmailkey = (data) => { + if (log) + console.log( + currentmod, + data.alias, + "-", + data.privatekey ? data.privatekey.substring(0, 10) : "", + "-", + data.tribe, + "-", + data.passphrase, + "-", + data.publickey ? data.publickey.substring(0, 10) : "", + "-", + data.email, + "-", + data.lg + ); + const person = { + alias: data.alias, + privatekey: data.privatekey, + tribe: data.tribe, + }; + + if (!data.publickey || !data.email || !data.privatekey) { + const personfile = `../../${data.tribe}/objects/persons/itm/${data.alias}.json`; + if (!fs.existsSync(personfile)) { + return { + status: 404, + ref: "Pagans", + msg: "persondoesnotexist", + data: { alias: data.alias, tribe: data.tribe }, + }; + } + const persondata = fs.readJsonSync(personfile); + if (!persondata.recoveryauth) { + return { + status: 404, + ref: "Pagans", + msg: "personhasnorecoveryauth", + data: { alias: data.alias, tribe: data.tribe, email: data.email }, + }; + } + person.email = persondata.recoveryauth.email; + person.publickey = persondata.recoveryauth.publickey; + person.privatekey = persondata.recoveryauth.privatekey; + person.passphrase = persondata.recoveryauth.passphrase; + } else { + person.email = data.email; + person.passphrase = data.passphrase; + person.publickey = data.publickey; + } + person.avecpassphrase = person.passphrase != ""; + let tplfile = `../../${data.tribe}/template/createidentity_${data.lg}.js`; + if (!fs.existsSync(tplfile)) { + tplfile = `../template/createidentity_${data.lg}.js`; + if (!fs.existsSync(tplfile)) { + return { + status: 406, + ref: "Pagans", + msg: "templatedoesnotexist", + data: { tplfile }, + }; + } + } + const tplemail = require(path.resolve(tplfile)); + /* + Remove from attachments for less user confusing + { + filename:`${person.alias}_publickey.txt`, + content: person.publickey, + contentType:"text/plain" + }, + */ + const maildata = { + from: tplemail.sender, + to: person.email, + subject: Mustache.render(tplemail.subject, person), + html: Mustache.render(tplemail.html, person), + text: Mustache.render(tplemail.text, person), + attachments: [ + { + filename: `${person.alias}_privatekey.txt`, + content: person.privatekey, + contentType: "text/plain", + }, + ], + }; + return Notifications.sendmail(maildata, data.tribe); +}; + +Pagans.authenticatedetachedSignature = async ( + alias, + pubK, + detachedSignature, + message +) => { + /** + * Check that a message was signed with a privateKey from a publicKey + * This is not necessary if isAuthenticated, but can be usefull to double check + * @TODO finish it and implement it also in /apxpagan.js for browser + * @alias {string} alias link to the publicKey + * @pubK {string} publiKey text format + * @detachedSignature {string} a detachedsignatured get from apx.detachedSignature + * @message {string} the message signed + * @return {boolean} true the message was signed by alias + * false the message was not signed by alias + */ + const publicKey = await openpgp.readKey({ armoredKey: pubK }); + const msg = await openpgp.createMessage({ text: message }); + const signature = await openpgp.readSignature({ + armoredSignature: detachedSignature, // parse detached signature + }); + const verificationResult = await openpgp.verify({ + msg, // Message object + signature, + verificationKeys: publicKey, + }); + const { verified, keyID } = verificationResult.signatures[0]; + try { + await verified; // throws on invalid signature + if (log) console.log(currentmod, "Signed by key id " + keyID.toHex()); + return KeyId.toHex().alias == alias; + } catch (e) { + if (log) + console.log(currentmod, "Signature could not be verified: " + e.message); + return false; + } +}; + +module.exports = Pagans; diff --git a/routes/notifications.js b/routes/notifications.js index d0026a4..a5ecfc6 100644 --- a/routes/notifications.js +++ b/routes/notifications.js @@ -44,13 +44,24 @@ router.get("/messages/:alias/:tribeId", (req, res) => { /** * @api {POST} adminapi/notifications/sendmail/:tribe/:template -Send personnalize emails * @apiName Sendmail - * @apiDescription Send personnalize email with data from template store in ../../{tribe}/template/{template}.json + * @apiDescription Send personnalize email with data from template store in ../../{tribe}/template/{template}.json and smtp in conf global or in /itm/{tribe}.json that must have valid parameter emailcontact must be authorized by the smtp + * "emailcontact": "noreply@smatchit.io", + * "smtp": { + * "host": "smtp-relay.brevo.com", + * "port": 587, + * "secure": false, + * "auth": { + * "user": "xx", + * "pass": "yy" + * } + * } + * @apiGroup Notifications * * @apiParam {string} template * @apiParam {string} tribe * @apiBody {array} emails to send (array of valid email) - * @apiBody {object} Data to personnalize template + * @apiBody {object} data to personnalize template * * @apiSuccess {object} notif content * @apiSuccessExample {json} Success-Response: @@ -59,24 +70,19 @@ router.get("/messages/:alias/:tribeId", (req, res) => { * bouture */ router.post( - "/sendmail/:tribe/:template/:await", + "/sendmail/:tribe/:template", checkHeaders, isAuthenticated, - (req, res) => { - const pathtpl = `../../${req.params.tribe}/${req.params.template}.js`; - if (fs.existsSync(pathtpl)) { - const sendemail = Notification.manageemail(req.body, pathtpl,req.params.tribe); - res.status(sendemail.status).send(sendemail); - } else { - res - .status(404) - .send({ - status: 404, - ref: "Notification", - msg: "templatenotfound", - data: { pathtpl }, - }); - } + async (req, res) => { + const data = req.body.data; + data.emailsto = req.body.emails; + const pathtpl = `../../${req.params.tribe}/template/${req.params.template}_${req.session.header.xlang}.js`; + const sendemail = await Notifications.manageemail( + data, + pathtpl, + req.params.tribe + ); + res.status(sendemail.status).send(sendemail); } ); @@ -99,14 +105,12 @@ router.post( router.post("/registeranonymous", checkHeaders, (req, res) => { //console.log("list registration ",req.body) if (!req.body.typekey || !["email", "telephone"].includes(req.body.typekey)) { - return res - .status(406) - .json({ - status: 406, - ref: "Notifications", - msg: "typekeyunknown", - data: { typekey: req.body.typekey }, - }); + return res.status(406).json({ + status: 406, + ref: "Notifications", + msg: "typekeyunknown", + data: { typekey: req.body.typekey }, + }); } const key = req.body.contactpoint ? req.body.contactpoint