summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.gitignore1
-rw-r--r--README.md2
-rw-r--r--config/domain-example.js3
-rwxr-xr-xmodels/Event.js14
-rwxr-xr-xpackage-lock.json5
-rwxr-xr-xpackage.json1
-rwxr-xr-xroutes.js195
-rwxr-xr-xviews/event.handlebars4
-rwxr-xr-xviews/eventgroup.handlebars6
-rwxr-xr-xviews/layouts/main.handlebars8
10 files changed, 186 insertions, 53 deletions
diff --git a/.gitignore b/.gitignore
index a31f385..16d3fa8 100755
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@
config/api.js
config/database.js
+config/domain.js
public/events/*
!public/events/.gitkeep
diff --git a/README.md b/README.md
index d40c0f0..6b4025d 100644
--- a/README.md
+++ b/README.md
@@ -8,5 +8,5 @@ You can use the publicly hosted version [here](https://gath.io).
1. Clone the repository
2. Open the directory, run `npm install`
-3. Rename `config/api-example.js` and `config/database-example.js` to `config/api.js` and `config/database.js`. For locally hosted versions, the local MongoDB configuration will work fine. To send emails, you need to set up a Sendgrid account and get an API key, which you should paste into `config/api.js`.
+3. Rename `config/api-example.js` and `config/database-example.js` and `config/domain-example.js` to `config/api.js` and `config/database.js` and `config/domain.js`. For locally hosted versions, the local MongoDB configuration will work fine. To send emails, you need to set up a Sendgrid account and get an API key, which you should paste into `config/api.js`.
4. Run `npm start`. Enjoy!
diff --git a/config/domain-example.js b/config/domain-example.js
new file mode 100644
index 0000000..3cb4c85
--- /dev/null
+++ b/config/domain-example.js
@@ -0,0 +1,3 @@
+module.exports = {
+ 'domain' : 'localhost:3000' // Your domain goes here
+};
diff --git a/models/Event.js b/models/Event.js
index 3c0bb8c..1de6088 100755
--- a/models/Event.js
+++ b/models/Event.js
@@ -159,7 +159,19 @@ const EventSchema = new mongoose.Schema({
maxAttendees: {
type: Number
},
- comments: [CommentSchema]
+ comments: [CommentSchema],
+ activityPubActor: {
+ type: String,
+ trim: true
+ },
+ publicKey: {
+ type: String,
+ trim: true
+ },
+ privateKey: {
+ type: String,
+ trim: true
+ }
});
module.exports = mongoose.model('Event', EventSchema);
diff --git a/package-lock.json b/package-lock.json
index 2e1d899..58c466e 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -2560,6 +2560,11 @@
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
+ "generate-rsa-keypair": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/generate-rsa-keypair/-/generate-rsa-keypair-0.2.1.tgz",
+ "integrity": "sha512-vxLfzfy6WbMLtkKV4AJtg7QH0ZqGGNkSYM6S0Q72Z70QXsztLklKFtX15te3YLIqmiQAYi3g3MWsTfXd6djkpg=="
+ },
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
diff --git a/package.json b/package.json
index fff1c2d..1368503 100755
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"express-jwt": "^5.3.1",
"express-session": "^1.15.6",
"express-validator": "^5.3.0",
+ "generate-rsa-keypair": "^0.2.1",
"greenlock": "^2.6.7",
"greenlock-express": "^2.6.7",
"ical": "^0.6.0",
diff --git a/routes.js b/routes.js
index ee723e2..c04077b 100755
--- a/routes.js
+++ b/routes.js
@@ -20,6 +20,11 @@ var moment = require('moment-timezone');
const marked = require('marked');
+const generateRSAKeypair = require('generate-rsa-keypair');
+
+const domain = require('./config/domain.js').domain;
+const contactEmail = require('./config/domain.js').email;
+
// Extra marked renderer (used to render plaintext event description for page metadata)
// Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/
// ? to ? helper
@@ -121,10 +126,49 @@ const deleteOldEvents = schedule.scheduleJob('59 23 * * *', function(fireDate){
});
+// ACTIVITYPUB HELPER FUNCTIONS
+function createWebfinger(eventID, domain) {
+ return {
+ 'subject': `acct:${eventID}@${domain}`,
+
+ 'links': [
+ {
+ 'rel': 'self',
+ 'type': 'application/activity+json',
+ 'href': `https://${domain}/${eventID}`
+ }
+ ]
+ };
+}
+
+function createActivityPubActor(eventID, domain, pubkey) {
+ return JSON.stringify({
+ '@context': [
+ 'https://www.w3.org/ns/activitystreams',
+ 'https://w3id.org/security/v1'
+ ],
+
+ 'id': `https://${domain}/u/${eventID}`,
+ 'type': 'Person',
+ 'preferredUsereventID': `${eventID}`,
+ 'inbox': `https://${domain}/api/inbox`,
+ 'followers': `https://${domain}/u/${eventID}/followers`,
+
+ 'publicKey': {
+ 'id': `https://${domain}/u/${eventID}#main-key`,
+ 'owner': `https://${domain}/u/${eventID}`,
+ 'publicKeyPem': pubkey
+ }
+ });
+}
+
// FRONTEND ROUTES
router.get('/', (req, res) => {
- res.render('home');
+ res.render('home', {
+ domain: domain,
+ email: contactEmail,
+ });
});
router.get('/new', (req, res) => {
@@ -174,6 +218,48 @@ router.get('/new/event/public', (req, res) => {
});
})
+router.get('/.well-known/webfinger', (req, res) => {
+ console.log(req.query);
+ let resource = req.query.resource;
+ if (!resource || !resource.includes('acct:')) {
+ return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.');
+ }
+ else {
+ // "foo@domain"
+ let activityPubAccount = resource.replace('acct:','');
+ // "foo"
+ let eventID = activityPubAccount.replace(/@.*/,'');
+ console.log(eventID);
+ Event.findOne({
+ id: eventID
+ })
+ .then((event) => {
+ if (!event) {
+ res.status(404);
+ res.render('404', { url: req.url });
+ }
+ else {
+ res.json(createWebfinger(eventID, domain));
+ }
+ })
+ .catch((err) => {
+ addToLog("renderWebfinger", "error", "Attempt to render webfinger for " + req.params.eventID + " failed with error: " + err);
+ console.log(err)
+ res.status(404);
+ res.render('404', { url: req.url });
+ return;
+ });
+ //let db = req.app.get('db');
+ //let result = db.prepare('select webfinger from accounts where name = ?').get(name);
+ //if (result === undefined) {
+ // return res.status(404).send(`No record found for ${name}.`);
+ //}
+ //else {
+ // res.json(JSON.parse(result.webfinger));
+ //}
+ }
+});
+
router.get('/:eventID', (req, res) => {
Event.findOne({
id: req.params.eventID
@@ -256,34 +342,51 @@ router.get('/:eventID', (req, res) => {
let metadata = {
title: event.name,
description: marked(event.description, { renderer: render_plain()}).split(" ").splice(0,40).join(" ").trim(),
- image: (eventHasCoverImage ? 'https://gath.io/events/' + event.image : null),
- url: 'https://gath.io/' + req.params.eventID
+ image: (eventHasCoverImage ? `https://${domain}/events/` + event.image : null),
+ url: `https://${domain}/` + req.params.eventID
};
- res.set("X-Robots-Tag", "noindex");
- res.render('event', {
- title: event.name,
- escapedName: escapedName,
- eventData: event,
- eventAttendees: eventAttendees,
- spotsRemaining: spotsRemaining,
- noMoreSpots: noMoreSpots,
- eventStartISO: eventStartISO,
- eventEndISO: eventEndISO,
- parsedLocation: parsedLocation,
- parsedStart: parsedStart,
- parsedEnd: parsedEnd,
- displayDate: displayDate,
- fromNow: fromNow,
- timezone: event.timezone,
- parsedDescription: parsedDescription,
- editingEnabled: editingEnabled,
- eventHasCoverImage: eventHasCoverImage,
- eventHasHost: eventHasHost,
- firstLoad: firstLoad,
- eventHasConcluded: eventHasConcluded,
- eventHasBegun: eventHasBegun,
- metadata: metadata,
- })
+ /////////////////////
+ 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', {
+ domain: domain,
+ email: contactEmail,
+ title: event.name,
+ escapedName: escapedName,
+ eventData: event,
+ eventAttendees: eventAttendees,
+ spotsRemaining: spotsRemaining,
+ noMoreSpots: noMoreSpots,
+ eventStartISO: eventStartISO,
+ eventEndISO: eventEndISO,
+ parsedLocation: parsedLocation,
+ parsedStart: parsedStart,
+ parsedEnd: parsedEnd,
+ displayDate: displayDate,
+ fromNow: fromNow,
+ timezone: event.timezone,
+ parsedDescription: parsedDescription,
+ editingEnabled: editingEnabled,
+ eventHasCoverImage: eventHasCoverImage,
+ eventHasHost: eventHasHost,
+ firstLoad: firstLoad,
+ eventHasConcluded: eventHasConcluded,
+ eventHasBegun: eventHasBegun,
+ metadata: metadata,
+ })
+ }
}
else {
res.status(404);
@@ -375,11 +478,12 @@ router.get('/group/:eventGroupID', (req, res) => {
let metadata = {
title: eventGroup.name,
description: marked(eventGroup.description, { renderer: render_plain()}).split(" ").splice(0,40).join(" ").trim(),
- image: (eventGroupHasCoverImage ? 'https://gath.io/events/' + eventGroup.image : null),
- url: 'https://gath.io/' + req.params.eventID
+ image: (eventGroupHasCoverImage ? `https://${domain}/events/` + eventGroup.image : null),
+ url: `https://${domain}/` + req.params.eventID
};
res.set("X-Robots-Tag", "noindex");
res.render('eventgroup', {
+ domain: domain,
title: eventGroup.name,
eventGroupData: eventGroup,
escapedName: escapedName,
@@ -445,6 +549,10 @@ router.post('/newevent', async (req, res) => {
isPartOfEventGroup = true;
}
}
+
+ // generate RSA keypair for ActivityPub
+ let pair = generateRSAKeypair();
+
const event = new Event({
id: eventID,
type: req.body.eventType,
@@ -466,7 +574,10 @@ router.post('/newevent', async (req, res) => {
showUsersList: req.body.guestlistCheckbox ? true : false,
usersCanComment: req.body.interactionCheckbox ? true : false,
maxAttendees: req.body.maxAttendees,
- firstLoad: true
+ firstLoad: true,
+ activityPubActor: createActivityPubActor(eventID, domain, pair.public),
+ publicKey: pair.public,
+ privateKey: pair.private
});
event.save()
.then((event) => {
@@ -477,7 +588,7 @@ router.post('/newevent', async (req, res) => {
to: req.body.creatorEmail,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-00330b8278ab463e9f88c16566487d97',
dynamic_template_data: {
@@ -547,7 +658,7 @@ router.post('/importevent', (req, res) => {
to: creatorEmail,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-00330b8278ab463e9f88c16566487d97',
dynamic_template_data: {
@@ -609,7 +720,7 @@ router.post('/neweventgroup', (req, res) => {
to: req.body.creatorEmail,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-4c5ddcb34ac44ec5b2313c6da4e405f3',
dynamic_template_data: {
@@ -701,7 +812,7 @@ router.post('/editevent/:eventID/:editToken', (req, res) => {
to: attendeeEmails,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-e21f3ca49d82476b94ddd8892c72a162',
dynamic_template_data: {
@@ -781,7 +892,7 @@ router.post('/editeventgroup/:eventGroupID/:editToken', (req, res) => {
// to: attendeeEmails,
// from: {
// name: 'Gathio',
- // email: 'notifications@gath.io',
+ // email: contactEmail,
// },
// templateId: 'd-e21f3ca49d82476b94ddd8892c72a162',
// dynamic_template_data: {
@@ -837,7 +948,7 @@ router.post('/deleteevent/:eventID/:editToken', (req, res) => {
to: attendeeEmails,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-e21f3ca49d82476b94ddd8892c72a162',
dynamic_template_data: {
@@ -964,7 +1075,7 @@ router.post('/attendevent/:eventID', (req, res) => {
to: req.body.attendeeEmail,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-977612474bba49c48b58e269f04f927c',
dynamic_template_data: {
@@ -999,7 +1110,7 @@ router.post('/unattendevent/:eventID', (req, res) => {
to: req.body.attendeeEmail,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-56c97755d6394c23be212fef934b0f1f',
dynamic_template_data: {
@@ -1034,7 +1145,7 @@ router.post('/removeattendee/:eventID/:attendeeID', (req, res) => {
to: req.body.attendeeEmail,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-f8ee9e1e2c8a48e3a329d1630d0d371f',
dynamic_template_data: {
@@ -1080,7 +1191,7 @@ router.post('/post/comment/:eventID', (req, res) => {
to: attendeeEmails,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-756d078561e047aba307155f02b6686d',
dynamic_template_data: {
@@ -1131,7 +1242,7 @@ router.post('/post/reply/:eventID/:commentID', (req, res) => {
to: attendeeEmails,
from: {
name: 'Gathio',
- email: 'notifications@gath.io',
+ email: contactEmail,
},
templateId: 'd-756d078561e047aba307155f02b6686d',
dynamic_template_data: {
diff --git a/views/event.handlebars b/views/event.handlebars
index 4d0cf28..e2529b8 100755
--- a/views/event.handlebars
+++ b/views/event.handlebars
@@ -76,8 +76,8 @@
<span class="fa-li">
<i class="fas fa-fw fa-share-square"></i>
</span>
- <a href="https://gath.io/{{eventData.id}}">gath.io/{{eventData.id}}</a>
- <button type="button" id="copyEventLink" class="eventInformationAction btn btn-outline-secondary btn-sm" data-clipboard-text="https://gath.io/{{eventData.id}}">
+ <a href="https://{{domain}}/{{eventData.id}}">{{domain}}/{{eventData.id}}</a>
+ <button type="button" id="copyEventLink" class="eventInformationAction btn btn-outline-secondary btn-sm" data-clipboard-text="https://{{domain}}/{{eventData.id}}">
<i class="fas fa-copy"></i> Copy
</button>
</li>
diff --git a/views/eventgroup.handlebars b/views/eventgroup.handlebars
index 00bae2c..dffb847 100755
--- a/views/eventgroup.handlebars
+++ b/views/eventgroup.handlebars
@@ -49,10 +49,10 @@
<span class="fa-li">
<i class="fas fa-share-square"></i>
</span>
- <a href="https://gath.io/group/{{eventGroupData.id}}">
- gath.io/group/{{eventGroupData.id}}
+ <a href="https://{{domain}}/group/{{eventGroupData.id}}">
+ {{domain}}/group/{{eventGroupData.id}}
</a>
- <button type="button" id="copyEventLink" class="eventInformationAction btn btn-outline-secondary btn-sm" data-clipboard-text="https://gath.io/group/{{eventGroupData.id}}">
+ <button type="button" id="copyEventLink" class="eventInformationAction btn btn-outline-secondary btn-sm" data-clipboard-text="https://{{domain}}/group/{{eventGroupData.id}}">
<i class="fas fa-copy"></i> Copy
</button>
</li>
diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars
index 4f0cfbc..3cde5d5 100755
--- a/views/layouts/main.handlebars
+++ b/views/layouts/main.handlebars
@@ -18,13 +18,13 @@
<meta property="og:image:height" content="260">
<meta property="og:description" content="{{#if metadata.description}}{{metadata.description}}{{else}}An easier, quicker, and much less privacy-invading way to make and share events{{/if}}">
<meta property="og:title" content="{{#if metadata.title}}{{metadata.title}} &middot; gathio{{else}}gathio{{/if}}">
- <meta property="og:url" content="{{#if metadata.url}}{{metadata.url}}{{else}}https://gath.io/{{/if}}">
- <meta property="og:image" content="{{#if metadata.image}}{{metadata.image}}{{else}}http://gath.io/og-image.jpg{{/if}}">
+ <meta property="og:url" content="{{#if metadata.url}}{{metadata.url}}{{else}}https://{{domain}}/{{/if}}">
+ <meta property="og:image" content="{{#if metadata.image}}{{metadata.image}}{{else}}http://{{domain}}/og-image.jpg{{/if}}">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{#if metadata.title}}{{metadata.title}} &middot; gathio{{else}}gathio{{/if}}">
<meta name="twitter:description" content="{{#if metadata.description}}{{metadata.description}}{{else}}An easier, quicker, and much less privacy-invading way to make and share events{{/if}}">
- <meta name="twitter:image" content="{{#if metadata.image}}{{metadata.image}}{{else}}http://gath.io/og-image.jpg{{/if}}">
+ <meta name="twitter:image" content="{{#if metadata.image}}{{metadata.image}}{{else}}http://{{domain}}/og-image.jpg{{/if}}">
<title>{{#if title}}{{title}} &middot; {{/if}}gathio</title>
@@ -73,7 +73,7 @@
</div>
<div id="footerContainer">
<small class="text-muted">
- <a href="https://github.com/lowercasename/gathio">GitHub</a> &middot; Made with <i class="far fa-heart"></i> by <a href="http://raphaelkabo.com">Raphael</a> &middot; Need help? <a href="mailto:support@gath.io">Email us</a>.<br />
+ <a href="https://github.com/lowercasename/gathio">GitHub</a> &middot; Made with <i class="far fa-heart"></i> by <a href="http://raphaelkabo.com">Raphael</a> &middot; Need help? <a href="mailto:{{email}}">Email us</a>.<br />
If you like gathio, you might like <strong><a href="http://sweet.sh/" style="color:#ed5e5e;">sweet</a></strong>, my utopian social network.
</small>
</div>