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
*.db
config.json
public/files

View file

@ -1,9 +1,13 @@
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(
})
await db.collection('streams').createIndex({
actor: 1,
_id: -1,
})
await db.collection('objects').findOneAndReplace(
{preferredUsername: 'dummy'},
{
id: `https://${domain}/u/dummy`,
@ -26,5 +30,4 @@ module.exports = function dbSetup (db, domain) {
returnOriginal: false,
}
)
})
}

View file

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

View file

@ -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']
Promise.all([
db.collection('objects').insertOne(req.body.object),
db.collection('streams').insertOne(req.body)
.then(() => res.status(200).send())
]).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 => {

View file

@ -6,5 +6,6 @@ module.exports = {
user: require('./user'),
message: require('./message'),
inbox: require('./inbox'),
outbox: require('./outbox'),
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';
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:','');
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');
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}.`);
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`)
}
else {
res.json(JSON.parse(result.webfinger));
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;

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) {
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;
}
@ -36,3 +31,7 @@ module.exports.arrayToCollection = function (arr, ordered) {
[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()
}