diff options
author | Darius Kazemi <darius.kazemi@gmail.com> | 2019-12-11 22:13:39 -0800 |
---|---|---|
committer | Darius Kazemi <darius.kazemi@gmail.com> | 2019-12-11 22:13:39 -0800 |
commit | 821017e5337612a37179b586d5506666ab70ab77 (patch) | |
tree | b2bfbfab6a04c1265cda82f641cc083be54267ae | |
parent | 51b42d13a370a9a79a618742c62de42c6cb666d8 (diff) |
follow Undo now works
-rwxr-xr-x | app.js | 2 | ||||
-rwxr-xr-x | models/Event.js | 14 | ||||
-rwxr-xr-x | package-lock.json | 51 | ||||
-rwxr-xr-x | package.json | 1 | ||||
-rwxr-xr-x | routes.js | 172 |
5 files changed, 194 insertions, 46 deletions
@@ -45,7 +45,7 @@ app.set('view engine', 'handlebars'); app.use(express.static('public')); // Router // - +app.use(bodyParser.json({ type: "application/activity+json" })); // support json encoded bodies app.use(bodyParser.urlencoded({ extended: true })); app.use('/', routes); diff --git a/models/Event.js b/models/Event.js index 1de6088..d68621a 100755 --- a/models/Event.js +++ b/models/Event.js @@ -15,6 +15,17 @@ const Attendees = new mongoose.Schema({ } }) +const Followers = new mongoose.Schema({ + followId: { + type: String, + trim: true + }, + account: { + type: String, + trim: true + } +}, {_id: false}) + const ReplySchema = new mongoose.Schema({ id: { type: String, @@ -171,7 +182,8 @@ const EventSchema = new mongoose.Schema({ privateKey: { type: String, trim: true - } + }, + followers: [Followers], }); module.exports = mongoose.model('Event', EventSchema); diff --git a/package-lock.json b/package-lock.json index 59aac7d..24f5ad7 100755 --- a/package-lock.json +++ b/package-lock.json @@ -586,9 +586,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", + "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==" }, "balanced-match": { "version": "1.0.0", @@ -923,8 +923,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -945,14 +944,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -967,20 +964,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1097,8 +1091,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1110,7 +1103,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1125,7 +1117,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1133,8 +1124,7 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", @@ -1238,8 +1228,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -1251,7 +1240,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -1373,7 +1361,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -1393,7 +1380,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1437,8 +1423,7 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", @@ -4205,9 +4190,9 @@ "dev": true }, "psl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", - "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.6.0.tgz", + "integrity": "sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==" }, "pstree.remy": { "version": "1.1.2", @@ -5299,9 +5284,9 @@ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, "v8-compile-cache": { "version": "2.0.3", diff --git a/package.json b/package.json index 1368503..5c3c0ed 100755 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "multer": "^1.4.1", "node-schedule": "^1.3.1", "randomstring": "^1.1.5", + "request": "^2.88.0", "shortid": "^2.2.14" }, "devDependencies": { @@ -21,6 +21,8 @@ var moment = require('moment-timezone'); const marked = require('marked'); const generateRSAKeypair = require('generate-rsa-keypair'); +const crypto = require('crypto'); +const request = require('request'); const domain = require('./config/domain.js').domain; const contactEmail = require('./config/domain.js').email; @@ -151,7 +153,7 @@ function createActivityPubActor(eventID, domain, pubkey) { 'id': `https://${domain}/${eventID}`, 'type': 'Person', 'preferredUsername': `${eventID}`, - 'inbox': `https://${domain}/api/inbox`, + 'inbox': `https://${domain}/activitypub/inbox`, 'followers': `https://${domain}/${eventID}/followers`, 'publicKey': { @@ -162,6 +164,63 @@ function createActivityPubActor(eventID, domain, pubkey) { }); } +function sendAcceptMessage(thebody, eventID, domain, req, res, targetDomain) { + const guid = crypto.randomBytes(16).toString('hex'); + let message = { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': `https://${domain}/${guid}`, + 'type': 'Accept', + 'actor': `https://${domain}/${eventID}`, + 'object': thebody, + }; + signAndSend(message, eventID, domain, req, res, targetDomain); +} + +function signAndSend(message, eventID, domain, req, res, targetDomain) { + // get the URI of the actor object and append 'inbox' to it + let inbox = message.object.actor+'/inbox'; + let inboxFragment = inbox.replace('https://'+targetDomain,''); + // get the private key + Event.findOne({ + id: eventID + }) + .then((event) => { + if (event) { + const privateKey = event.privateKey; + const signer = crypto.createSign('sha256'); + let d = new Date(); + let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}`; + signer.update(stringToSign); + signer.end(); + const signature = signer.sign(privateKey); + const signature_b64 = signature.toString('base64'); + const header = `keyId="https://${domain}/${eventID}",headers="(request-target) host date",signature="${signature_b64}"`; + request({ + url: inbox, + headers: { + 'Host': targetDomain, + 'Date': d.toUTCString(), + 'Signature': header + }, + method: 'POST', + json: true, + body: message + }, function (error, response){ + if (error) { + console.log('Error:', error, response.body); + } + else { + console.log('Response:', response.body); + } + }); + return res.status(200); + } + else { + return res.status(404).send(`No record found for ${eventID}.`); + } + }); +} + // FRONTEND ROUTES router.get('/', (req, res) => { @@ -345,19 +404,9 @@ router.get('/:eventID', (req, res) => { image: (eventHasCoverImage ? `https://${domain}/events/` + event.image : null), url: `https://${domain}/` + req.params.eventID }; - ///////////////////// if (req.headers.accept && (req.headers.accept.includes('application/activity+json') || req.headers.accept.includes('application/json') || req.headers.accept.includes('application/json+ld'))) { res.json(JSON.parse(event.activityPubActor)); - - //let tempActor = JSON.parse(result.actor); - //// Added this followers URI for Pleroma compatibility, see https://github.com/dariusk/rss-to-activitypub/issues/11#issuecomment-471390881 - //// New Actors should have this followers URI but in case of migration from an old version this will add it in on the fly - //if (tempActor.followers === undefined) { - // tempActor.followers = `https://${domain}/u/${username}/followers`; - //} - //res.json(tempActor); } - ///////////////// else { res.set("X-Robots-Tag", "noindex"); res.render('event', { @@ -403,6 +452,37 @@ router.get('/:eventID', (req, res) => { }); }) +router.get('/:eventID/followers', (req, res) => { + const eventID = req.params.eventID; + Event.findOne({ + id: eventID + }) + .then((event) => { + if (event) { + console.log(event.followers); + const followers = event.followers.map(el => el.account); + console.log(followers) + let followersCollection = { + "type": "OrderedCollection", + "totalItems": followers.length, + "id": `https://${domain}/${eventID}/followers`, + "first": { + "type": "OrderedCollectionPage", + "totalItems": followers.length, + "partOf": `https://${domain}/${eventID}/followers`, + "orderedItems": followers, + "id": `https://${domain}/${eventID}/followers?page=1` + }, + "@context":["https://www.w3.org/ns/activitystreams"] + }; + return res.json(followersCollection); + } + else { + return res.status(400).send('Bad request.'); + } + }) +}) + router.get('/group/:eventGroupID', (req, res) => { EventGroup.findOne({ id: req.params.eventGroupID @@ -1295,6 +1375,76 @@ router.post('/deletecomment/:eventID/:commentID/:editToken', (req, res) => { .catch((err) => { res.send('Sorry! Something went wrong: ' + err); addToLog("deleteComment", "error", "Attempt to delete comment " + req.params.commentID + "from event " + req.params.eventID + " failed with error: " + err);}); }); +router.post('/activitypub/inbox', (req, res) => { + console.log('got a inbox message') + const myURL = new URL(req.body.actor); + let targetDomain = myURL.hostname; + // if a Follow activity hits the inbox + if (typeof req.body.object === 'string' && req.body.type === 'Follow') { + console.log('follow!') + let eventID = req.body.object.replace(`https://${domain}/`,''); + sendAcceptMessage(req.body, eventID, domain, req, res, targetDomain); + // Add the user to the DB of accounts that follow the account + console.log(req.body) + + const newFollower = { + account: req.body.actor, + followId: req.body.id + }; + + Event.findOne({ + id: eventID, + }, function(err,event) { + console.log(event.followers) + // if this account is NOT already in our followers list, add it + if (!event.followers.map(el => el.account).includes(req.body.actor)) { + event.followers.push(newFollower); + console.log(event.followers) + event.save() + .then(() => { + addToLog("addEventFollower", "success", "Follower added to event " + eventID); + console.log('successful follower add') + }) + .catch((err) => { res.send('Database error, please try again :('); addToLog("addEventFollower", "error", "Attempt to add follower to event " + eventID + " failed with error: " + err); + console.log('error', err) + }); + } + }); + } + // if an Undo activity with a Follow object hits the inbox + if (req.body && req.body.type === 'Undo' && req.body.object && req.body.object.type === 'Follow') { + console.log('undo follow!') + console.log(req.body) + // get the record of all followers for this account + let eventID = req.body.object.object.replace(`https://${domain}/`,''); + Event.findOne({ + id: eventID, + }, function(err,event) { + // check to see if the Follow object's id matches the id we have on record + console.log(event.followers) + // is this even someone who follows us + const indexOfFollower = event.followers.findIndex(el => {console.log(el.account, req.body.object.actor); return el.account === req.body.object.actor;}); + console.log(indexOfFollower) + if (indexOfFollower !== -1) { + // does the id we have match the id we are being given + if (event.followers[indexOfFollower].followId === req.body.object.id) { + // we have a match and can trust the Undo! remove this person from the followers list + event.followers.splice(indexOfFollower, 1); + console.log('new', indexOfFollower, event.followers); + event.save() + .then(() => { + addToLog("removeEventFollower", "success", "Follower removed from event " + eventID); + console.log('successful follower removal') + }) + .catch((err) => { res.send('Database error, please try again :('); addToLog("removeEventFollower", "error", "Attempt to remove follower from event " + eventID + " failed with error: " + err); + console.log('error', err) + }); + } + } + }); + } +}); + router.use(function(req, res, next){ res.status(404); res.render('404', { url: req.url }); |