summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xapp.js2
-rwxr-xr-xmodels/Event.js14
-rwxr-xr-xpackage-lock.json51
-rwxr-xr-xpackage.json1
-rwxr-xr-xroutes.js172
5 files changed, 194 insertions, 46 deletions
diff --git a/app.js b/app.js
index a8d2889..ab4eb56 100755
--- a/app.js
+++ b/app.js
@@ -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": {
diff --git a/routes.js b/routes.js
index e442e1a..0371d96 100755
--- a/routes.js
+++ b/routes.js
@@ -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 });