automatic actor creation on finger, user lookup. followers collection endpoint populated from inbox activity side effects

This commit is contained in:
Will Murphy 2019-09-16 20:58:32 -05:00
parent 0a3c758ad7
commit e4c150b840
5 changed files with 71 additions and 49 deletions

View file

@ -2,14 +2,20 @@ const utils = require('../utils')
const crypto = require('crypto')
module.exports = async function dbSetup (db, domain) {
// inbox
await db.collection('streams').createIndex({
_target: 1,
_id: -1,
})
// outbox
await db.collection('streams').createIndex({
actor: 1,
_id: -1,
})
// object lookup
await db.collection('objects').createIndex({
id: 1
})
const dummyUser = await utils.createLocalActor('dummy', 'Person')
await db.collection('objects').findOneAndReplace(
{preferredUsername: 'dummy'},

View file

@ -10,15 +10,8 @@ router.get('/:name', async function (req, res) {
return res.status(400).send('Bad request.');
}
else {
let objs = req.app.get('objs');
let db = req.app.get('db')
const id = utils.userNameToIRI(name)
console.log(`looking up '${id}'`)
const user = await db.collection('objects')
.find({type: 'Person', id: id})
.limit(1)
.project({_id: 0, _meta: 0})
.next()
const user = await utils.getOrCreateActor(name, db)
if (user) {
return res.json(toJSONLD(user))
}
@ -26,33 +19,28 @@ router.get('/:name', async function (req, res) {
}
});
// router.get('/:name/followers', function (req, res) {
// let name = req.params.name;
// if (!name) {
// return res.status(400).send('Bad request.');
// }
// else {
// let db = req.app.get('db');
// let domain = req.app.get('domain');
// let result = db.prepare('select followers from accounts where name = ?').get(`${name}@${domain}`);
// console.log(result);
// result.followers = result.followers || '[]';
// let followers = JSON.parse(result.followers);
// let followersCollection = {
// "type":"OrderedCollection",
// "totalItems":followers.length,
// "id":`https://${domain}/u/${name}/followers`,
// "first": {
// "type":"OrderedCollectionPage",
// "totalItems":followers.length,
// "partOf":`https://${domain}/u/${name}/followers`,
// "orderedItems": followers,
// "id":`https://${domain}/u/${name}/followers?page=1`
// },
// "@context":["https://www.w3.org/ns/activitystreams"]
// };
// res.json(toJSONLD(followersCollection));
// }
// });
router.get('/:name/followers', function (req, res) {
let name = req.params.name;
if (!name) {
return res.status(400).send('Bad request.');
}
const db = req.app.get('db')
db.collection('streams')
.find({
type: 'Follow',
_target: name,
'object.id': utils.usernameToIRI(name)
})
.project({_id: 0, actor: 1})
.toArray()
.then(follows => {
const followers = follows.map(utils.actorFromActivity)
return res.json(utils.arrayToCollection(followers))
})
.catch(err => {
console.log(err)
return res.status(500).send()
})
});
module.exports = router;

View file

@ -13,12 +13,7 @@ router.get('/', function (req, res) {
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()
utils.getOrCreateActor(acct[1], db)
.then(result => {
if (!result) {
return res.status(404).send(`${acct[1]}@${acct[2]} not found`)
@ -29,7 +24,7 @@ router.get('/', function (req, res) {
{
'rel': 'self',
'type': 'application/activity+json',
'href': userId
'href': result.id
}
]
}

View file

@ -1,9 +1,10 @@
const crypto = require('crypto')
const {promisify} = require('util')
const {ASContext} = require('./consts')
module.exports.validators = require('./validators');
const config = require('../config.json')
module.exports.validators = require('./validators');
function isObject(value) {
return value && typeof value === 'object' && value.constructor === Object
}
@ -34,13 +35,13 @@ module.exports.arrayToCollection = function (arr, ordered) {
}
}
function userNameToIRI (user) {
function usernameToIRI (user) {
return `https://${config.DOMAIN}/u/${user}`
}
module.exports.userNameToIRI = userNameToIRI
module.exports.usernameToIRI = usernameToIRI
const generateKeyPairPromise = promisify(crypto.generateKeyPair)
module.exports.createLocalActor = function (name, type) {
function createLocalActor (name, type) {
return generateKeyPairPromise('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
@ -54,7 +55,7 @@ module.exports.createLocalActor = function (name, type) {
passphrase: config.KEYPASS
}
}).then(pair => {
const actorBase = userNameToIRI(name);
const actorBase = usernameToIRI(name);
return {
_meta: {
privateKey: pair.privateKey,
@ -81,3 +82,35 @@ module.exports.createLocalActor = function (name, type) {
}
})
}
module.exports.createLocalActor = createLocalActor
async function getOrCreateActor(preferredUsername, db) {
const id = usernameToIRI(preferredUsername)
let user = await db.collection('objects')
.find({id: id})
.limit(1)
.project({_id: 0, _meta: 0})
.next()
if (user) {
return user
}
// auto create groups whenever an unknown actor is referenced
user = await createLocalActor(preferredUsername, 'Group')
await db.collection('objects').insertOne(user)
// only executed on success
delete user._meta
delete user._id
return user
}
module.exports.getOrCreateActor = getOrCreateActor
function actorFromActivity (activity) {
if (Object.prototype.toString.call(activity.actor) === '[object String]') {
return activity.actor
}
if (activity.actor.type === 'Link') {
return activity.actor.href
}
return activity.actor.id
}
module.exports.actorFromActivity = actorFromActivity

View file

@ -28,7 +28,7 @@ module.exports.outboxActivity = function outboxActivity (req, res, next) {
if (!validateObject(req.body)) {
return res.status(400).send('Invalid activity')
}
const newID = ObjectId()
const newID = new ObjectId()
req.body = {
_id: newID,
'@context': ASContext,