diff --git a/.gitignore b/.gitignore index 8d3e807..e3d61be 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ package-lock.json *.db config.json +public/files diff --git a/db/setup.js b/db/setup.js index 2975b6c..8651066 100644 --- a/db/setup.js +++ b/db/setup.js @@ -1,30 +1,33 @@ -module.exports = function dbSetup (db, domain) { - return db.collection('streams').createIndex({ +module.exports = async function dbSetup (db, domain) { + await db.collection('streams').createIndex({ _target: 1, _id: -1, - }).then(() => { - return db.collection('objects').findOneAndReplace( - {preferredUsername: 'dummy'}, - { - id: `https://${domain}/u/dummy`, - "type": "Person", - "following": `https://${domain}/u/dummy/following`, - "followers": `https://${domain}/u/dummy/followers`, - "liked": `https://${domain}/u/dummy/liked`, - "inbox": `https://${domain}/u/dummy/inbox`, - "outbox": `https://${domain}/u/dummy/outbox`, - "preferredUsername": "dummy", - "name": "Dummy Person", - "summary": "Gotta have someone in the db", - "icon": `http://${domain}/f/dummy.png`, - attachment: [ - `http://${domain}/f/dummy.glb` - ] - }, - { - upsert: true, - returnOriginal: false, - } - ) }) + await db.collection('streams').createIndex({ + actor: 1, + _id: -1, + }) + await db.collection('objects').findOneAndReplace( + {preferredUsername: 'dummy'}, + { + id: `https://${domain}/u/dummy`, + "type": "Person", + "following": `https://${domain}/u/dummy/following`, + "followers": `https://${domain}/u/dummy/followers`, + "liked": `https://${domain}/u/dummy/liked`, + "inbox": `https://${domain}/u/dummy/inbox`, + "outbox": `https://${domain}/u/dummy/outbox`, + "preferredUsername": "dummy", + "name": "Dummy Person", + "summary": "Gotta have someone in the db", + "icon": `http://${domain}/f/dummy.png`, + attachment: [ + `http://${domain}/f/dummy.glb` + ] + }, + { + upsert: true, + returnOriginal: false, + } + ) } \ No newline at end of file diff --git a/index.js b/index.js index 8e75bcb..ed51eca 100644 --- a/index.js +++ b/index.js @@ -83,7 +83,8 @@ app.use('/.well-known/webfinger', cors(), routes.webfinger); app.use('/u', cors(), routes.user); app.use('/m', cors(), routes.message); // app.use('/api/inbox', cors(), routes.inbox); -app.use('/u/:name/inbox', routes.inbox); +app.use('/u/:name/inbox', routes.inbox) +app.use('/u/:name/outbox', routes.outbox) app.use('/admin', express.static('public/admin')); app.use('/f', express.static('public/files')); app.use('/hubs', express.static('../hubs/dist')); diff --git a/routes/inbox.js b/routes/inbox.js index da25075..e476306 100644 --- a/routes/inbox.js +++ b/routes/inbox.js @@ -1,25 +1,26 @@ -const express = require('express'), - router = express.Router(); +const express = require('express') +const router = express.Router() const utils = require('../utils') -router.post('/', function (req, res) { +router.post('/', utils.validators.activity, function (req, res) { const db = req.app.get('db'); req.body._target = req.user - delete req.body['@context'] - db.collection('streams').insertOne(req.body) - .then(() => res.status(200).send()) + Promise.all([ + db.collection('objects').insertOne(req.body.object), + db.collection('streams').insertOne(req.body) + ]).then(() => res.status(200).send()) .catch(err => { console.log(err) res.status(500).send() }) }); -router.get('/', async function (req, res) { +router.get('/', function (req, res) { const db = req.app.get('db'); db.collection('streams') .find({_target: req.user}) .sort({_id: -1}) - .project({_id: 0, _target: 0}) + .project({_id: 0, _target: 0, '@context': 0, 'object._id': 0, 'object.@context': 0}) .toArray() .then(stream => res.json(utils.arrayToCollection(stream, true))) .catch(err => { diff --git a/routes/index.js b/routes/index.js index afd9141..fec804c 100644 --- a/routes/index.js +++ b/routes/index.js @@ -6,5 +6,6 @@ module.exports = { user: require('./user'), message: require('./message'), inbox: require('./inbox'), + outbox: require('./outbox'), webfinger: require('./webfinger'), }; diff --git a/routes/outbox.js b/routes/outbox.js new file mode 100644 index 0000000..5f8658b --- /dev/null +++ b/routes/outbox.js @@ -0,0 +1,32 @@ +const express = require('express') +const router = express.Router() +const utils = require('../utils') + +router.post('/', utils.validators.outboxActivity, function (req, res) { + const db = req.app.get('db'); + Promise.all([ + db.collection('objects').insertOne(req.body.object), + db.collection('streams').insertOne(req.body) + ]).then(() => res.status(200).send()) + .catch(err => { + console.log(err) + res.status(500).send() + }) +}); + +router.get('/', function (req, res) { + const db = req.app.get('db'); + db.collection('streams') + .find({actor: utils.userNameToIRI(req.user)}) + .sort({_id: -1}) + .project({_id: 0, _target: 0, 'object._id': 0, 'object.@context': 0}) + .toArray() + .then(stream => res.json(utils.arrayToCollection(stream, true))) + .catch(err => { + console.log(err) + return res.status(500).send() + }) + ; +}) + +module.exports = router; diff --git a/routes/webfinger.js b/routes/webfinger.js index c743d84..df88066 100644 --- a/routes/webfinger.js +++ b/routes/webfinger.js @@ -1,23 +1,44 @@ 'use strict'; const express = require('express'), router = express.Router(); - +const utils = require('../utils') +const acctReg = /acct:[@~]?([^@]+)@?(.*)/ router.get('/', function (req, res) { let resource = req.query.resource; - if (!resource || !resource.includes('acct:')) { + let acct = acctReg.exec(resource) + if (!acct || acct.length < 2) { return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.'); } - else { - let name = resource.replace('acct:',''); - 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)); - } + if (acct[2] && acct[2].toLowerCase() !== req.app.get('domain').toLowerCase()) { + return res.status(400).send('Requested user is not from this domain') } + let db = req.app.get('db'); + const userId = utils.userNameToIRI(acct[1]); + db.collection('objects') + .find({id: userId}) + .limit(1) + .project({_id: 0}) + .next() + .then(result => { + if (!result) { + return res.status(404).send(`${acct[1]}@${acct[2]} not found`) + } + const finger = { + 'subject': resource, + 'links': [ + { + 'rel': 'self', + 'type': 'application/activity+json', + 'href': userId + } + ] + } + return res.json(finger) + }) + .catch(err => { + console.log(err); + res.status(500).send() + }) }); module.exports = router; diff --git a/utils/consts.js b/utils/consts.js new file mode 100644 index 0000000..0a2839e --- /dev/null +++ b/utils/consts.js @@ -0,0 +1,3 @@ +module.exports = { + ASContext: 'https://www.w3.org/ns/activitystreams', +} diff --git a/utils/db.js b/utils/db.js new file mode 100644 index 0000000..e69de29 diff --git a/utils/index.js b/utils/index.js index 71b8fc9..29db058 100644 --- a/utils/index.js +++ b/utils/index.js @@ -1,12 +1,7 @@ -const ASContext = 'https://www.w3.org/ns/activitystreams'; +const { ASContext } = require('./consts') +module.exports.validators = require('./validators'); +const config = require('../config.json') -function convertId(obj) { - if (obj._id) { - obj.id = obj._id - delete obj._id - } - return obj -} function isObject(value) { return value && typeof value === 'object' && value.constructor === Object } @@ -23,7 +18,7 @@ function traverseObject(obj, f) { return f(obj); } module.exports.toJSONLD = function (obj) { - obj['@context'] = ASContext; + obj['@context'] = obj['@context'] || ASContext; return obj; } @@ -35,4 +30,8 @@ module.exports.arrayToCollection = function (arr, ordered) { type: ordered ? 'orderedCollection' : 'collection', [ordered ? 'orderedItems' : 'items']: arr, } +} + +module.exports.userNameToIRI = function (user) { + return `https://${config.DOMAIN}/u/${user}` } \ No newline at end of file diff --git a/utils/validators.js b/utils/validators.js new file mode 100644 index 0000000..be3670e --- /dev/null +++ b/utils/validators.js @@ -0,0 +1,47 @@ +const {ObjectId} = require('mongodb') +// const activities = ['Create', ] +const {ASContext} = require('./consts') + +function validateObject (object) { + if (object && object.id) { + object['@context'] = object['@context'] || ASContext + return true + } +} + +function validateActivity (object) { + if (object && object.id && object.actor) { + return true + } +} + +module.exports.activity = function activity (req, res, next) { + // TODO real validation + if (!validateActivity(req.body)) { + return res.status(400).send('Invalid activity') + } + next() +} + +module.exports.outboxActivity = function outboxActivity (req, res, next) { + if (!validateActivity(req.body)) { + if (!validateObject(req.body)) { + return res.status(400).send('Invalid activity') + } + const newID = ObjectId() + req.body = { + _id: newID, + '@context': ASContext, + type: 'Create', + id: `http://${req.app.get('domain')}/o/${newID.toHexString()}`, + actor: req.body.attributedTo, + object: req.body, + published: new Date().toISOString(), + to: req.body.to, + cc: req.body.cc, + bcc: req.body.cc, + audience: req.body.audience, + } + } + next() +} \ No newline at end of file