inbox and outbox get and post, webfinger

This commit is contained in:
Will Murphy 2019-09-14 19:00:26 -05:00
parent cb137ffcb1
commit d7c733ae95
11 changed files with 165 additions and 56 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@ node_modules/
package-lock.json package-lock.json
*.db *.db
config.json config.json
public/files

View file

@ -1,30 +1,33 @@
module.exports = function dbSetup (db, domain) { module.exports = async function dbSetup (db, domain) {
return db.collection('streams').createIndex({ await db.collection('streams').createIndex({
_target: 1, _target: 1,
_id: -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,
}
)
} }

View file

@ -83,7 +83,8 @@ app.use('/.well-known/webfinger', cors(), routes.webfinger);
app.use('/u', cors(), routes.user); app.use('/u', cors(), routes.user);
app.use('/m', cors(), routes.message); app.use('/m', cors(), routes.message);
// app.use('/api/inbox', cors(), routes.inbox); // 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('/admin', express.static('public/admin'));
app.use('/f', express.static('public/files')); app.use('/f', express.static('public/files'));
app.use('/hubs', express.static('../hubs/dist')); app.use('/hubs', express.static('../hubs/dist'));

View file

@ -1,25 +1,26 @@
const express = require('express'), const express = require('express')
router = express.Router(); const router = express.Router()
const utils = require('../utils') const utils = require('../utils')
router.post('/', function (req, res) { router.post('/', utils.validators.activity, function (req, res) {
const db = req.app.get('db'); const db = req.app.get('db');
req.body._target = req.user req.body._target = req.user
delete req.body['@context'] Promise.all([
db.collection('streams').insertOne(req.body) db.collection('objects').insertOne(req.body.object),
.then(() => res.status(200).send()) db.collection('streams').insertOne(req.body)
]).then(() => res.status(200).send())
.catch(err => { .catch(err => {
console.log(err) console.log(err)
res.status(500).send() res.status(500).send()
}) })
}); });
router.get('/', async function (req, res) { router.get('/', function (req, res) {
const db = req.app.get('db'); const db = req.app.get('db');
db.collection('streams') db.collection('streams')
.find({_target: req.user}) .find({_target: req.user})
.sort({_id: -1}) .sort({_id: -1})
.project({_id: 0, _target: 0}) .project({_id: 0, _target: 0, '@context': 0, 'object._id': 0, 'object.@context': 0})
.toArray() .toArray()
.then(stream => res.json(utils.arrayToCollection(stream, true))) .then(stream => res.json(utils.arrayToCollection(stream, true)))
.catch(err => { .catch(err => {

View file

@ -6,5 +6,6 @@ module.exports = {
user: require('./user'), user: require('./user'),
message: require('./message'), message: require('./message'),
inbox: require('./inbox'), inbox: require('./inbox'),
outbox: require('./outbox'),
webfinger: require('./webfinger'), webfinger: require('./webfinger'),
}; };

32
routes/outbox.js Normal file
View file

@ -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;

View file

@ -1,23 +1,44 @@
'use strict'; 'use strict';
const express = require('express'), const express = require('express'),
router = express.Router(); router = express.Router();
const utils = require('../utils')
const acctReg = /acct:[@~]?([^@]+)@?(.*)/
router.get('/', function (req, res) { router.get('/', function (req, res) {
let resource = req.query.resource; 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.'); return res.status(400).send('Bad request. Please make sure "acct:USER@DOMAIN" is what you are sending as the "resource" query parameter.');
} }
else { if (acct[2] && acct[2].toLowerCase() !== req.app.get('domain').toLowerCase()) {
let name = resource.replace('acct:',''); return res.status(400).send('Requested user is not from this domain')
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));
}
} }
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; module.exports = router;

3
utils/consts.js Normal file
View file

@ -0,0 +1,3 @@
module.exports = {
ASContext: 'https://www.w3.org/ns/activitystreams',
}

0
utils/db.js Normal file
View file

View file

@ -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) { function isObject(value) {
return value && typeof value === 'object' && value.constructor === Object return value && typeof value === 'object' && value.constructor === Object
} }
@ -23,7 +18,7 @@ function traverseObject(obj, f) {
return f(obj); return f(obj);
} }
module.exports.toJSONLD = function (obj) { module.exports.toJSONLD = function (obj) {
obj['@context'] = ASContext; obj['@context'] = obj['@context'] || ASContext;
return obj; return obj;
} }
@ -35,4 +30,8 @@ module.exports.arrayToCollection = function (arr, ordered) {
type: ordered ? 'orderedCollection' : 'collection', type: ordered ? 'orderedCollection' : 'collection',
[ordered ? 'orderedItems' : 'items']: arr, [ordered ? 'orderedItems' : 'items']: arr,
} }
}
module.exports.userNameToIRI = function (user) {
return `https://${config.DOMAIN}/u/${user}`
} }

47
utils/validators.js Normal file
View file

@ -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()
}