package details, lint

This commit is contained in:
Will Murphy 2019-09-22 00:20:37 -05:00
parent 03b0ae732e
commit a8230136f8
22 changed files with 302 additions and 339 deletions

View file

@ -1,93 +1,68 @@
const { promisify } = require('util')
const path = require('path') const path = require('path')
const express = require('express'); const express = require('express')
const MongoClient = require('mongodb').MongoClient; const MongoClient = require('mongodb').MongoClient
const fs = require('fs'); const fs = require('fs')
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
const cors = require('cors') const cors = require('cors')
const http = require('http')
const https = require('https') const https = require('https')
const basicAuth = require('express-basic-auth');
const routes = require('./routes') const routes = require('./routes')
const pub = require('./pub') const pub = require('./pub')
const config = require('./config.json') const config = require('./config.json')
const { USER, PASS, DOMAIN, KEY_PATH, CERT_PATH, PORT, PORT_HTTPS } = config const { DOMAIN, KEY_PATH, CERT_PATH, PORT, PORT_HTTPS } = config
const app = express(); const app = express()
// Connection URL // Connection URL
const url = 'mongodb://localhost:27017'; const url = 'mongodb://localhost:27017'
const store = require('./store'); const store = require('./store')
// Database Name // Database Name
const dbName = 'test'; const dbName = 'test'
// Create a new MongoClient // Create a new MongoClient
const client = new MongoClient(url, {useUnifiedTopology: true}); const client = new MongoClient(url, { useUnifiedTopology: true })
let db; let db
const sslOptions = {
let sslOptions; key: fs.readFileSync(path.join(__dirname, KEY_PATH)),
cert: fs.readFileSync(path.join(__dirname, CERT_PATH))
sslOptions = {
key: fs.readFileSync(path.join(__dirname, KEY_PATH)),
cert: fs.readFileSync(path.join(__dirname, CERT_PATH))
};
app.set('domain', DOMAIN);
app.set('port', process.env.PORT || PORT);
app.set('port-https', process.env.PORT_HTTPS || PORT_HTTPS);
app.use(bodyParser.json({type: [
'application/activity+json',
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
]})); // support json encoded bodies
app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
// basic http authorizer
let basicUserAuth = basicAuth({
authorizer: asyncAuthorizer,
authorizeAsync: true,
challenge: true
});
function asyncAuthorizer(username, password, cb) {
let isAuthorized = false;
const isPasswordAuthorized = username === USER;
const isUsernameAuthorized = password === PASS;
isAuthorized = isPasswordAuthorized && isUsernameAuthorized;
if (isAuthorized) {
return cb(null, true);
}
else {
return cb(null, false);
}
} }
app.set('domain', DOMAIN)
app.set('port', process.env.PORT || PORT)
app.set('port-https', process.env.PORT_HTTPS || PORT_HTTPS)
app.use(bodyParser.json({
type: [
'application/activity+json',
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
]
})) // support json encoded bodies
app.use(bodyParser.urlencoded({ extended: true })) // support encoded bodies
app.param('name', function (req, res, next, id) { app.param('name', function (req, res, next, id) {
req.user = id req.user = id
next() next()
}) })
app.get('/', (req, res) => res.send('Hello World!')); app.get('/', (req, res) => res.send('Hello World!'))
// admin page // admin page
app.use('/.well-known/webfinger', cors(), routes.webfinger); 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('/u/:name/inbox', routes.inbox) app.use('/u/:name/inbox', routes.inbox)
app.use('/u/:name/outbox', routes.outbox) 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'));
// Use connect method to connect to the Server // Use connect method to connect to the Server
client.connect({useNewUrlParser: true}) client.connect({ useNewUrlParser: true })
.then(() => { .then(() => {
console.log("Connected successfully to server"); console.log('Connected successfully to server')
db = client.db(dbName); db = client.db(dbName)
app.set('db', db); app.set('db', db)
return pub.actor.createLocalActor('dummy', 'Person') return pub.actor.createLocalActor('dummy', 'Person')
}) })
.then(dummy => { .then(dummy => {
@ -95,9 +70,9 @@ client.connect({useNewUrlParser: true})
}) })
.then(() => { .then(() => {
https.createServer(sslOptions, app).listen(app.get('port-https'), function () { https.createServer(sslOptions, app).listen(app.get('port-https'), function () {
console.log('Express server listening on port ' + app.get('port-https')); console.log('Express server listening on port ' + app.get('port-https'))
}); })
}) })
.catch(err => { .catch(err => {
throw new Error(err) throw new Error(err)
}); })

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict'
// middleware // middleware
module.exports = { module.exports = {
validators: require('./validators'), validators: require('./validators'),
security: require('./security'), security: require('./security')
}; }

View file

@ -3,25 +3,25 @@ const httpSignature = require('http-signature')
const pub = require('../pub') const pub = require('../pub')
// http communication middleware // http communication middleware
module.exports = { module.exports = {
verifySignature, verifySignature
} }
async function verifySignature (req, res, next) { async function verifySignature (req, res, next) {
if (!req.get('authorization')) { if (!req.get('authorization')) {
// support for apps not using signature extension to ActivityPub // support for apps not using signature extension to ActivityPub
// TODO check if actor has a publicKey and require signature // TODO check if actor has a publicKey and require signature
return next() return next()
} }
// workaround for node-http-signature#87 // workaround for node-http-signature#87
const tempUrl = req.url const tempUrl = req.url
req.url = req.originalUrl req.url = req.originalUrl
const sigHead = httpSignature.parse(req) const sigHead = httpSignature.parse(req)
req.url = tempUrl req.url = tempUrl
const signer = await pub.object.resolveObject(sigHead.keyId, req.app.get('db')) const signer = await pub.object.resolveObject(sigHead.keyId, req.app.get('db'))
const valid = httpSignature.verifySignature(sigHead, signer.publicKey.publicKeyPem) const valid = httpSignature.verifySignature(sigHead, signer.publicKey.publicKeyPem)
console.log('signature validation', valid) console.log('signature validation', valid)
if (!valid) { if (!valid) {
return res.status(400).send('Invalid http signature') return res.status(400).send('Invalid http signature')
} }
next() next()
} }

View file

@ -1,47 +1,47 @@
const {ObjectId} = require('mongodb') const { ObjectId } = require('mongodb')
// const activities = ['Create', ] // const activities = ['Create', ]
const pub = require('../pub') const pub = require('../pub')
function validateObject (object) { function validateObject (object) {
if (object && object.id) { if (object && object.id) {
object['@context'] = object['@context'] || pub.consts.ASContext object['@context'] = object['@context'] || pub.consts.ASContext
return true return true
} }
} }
function validateActivity (object) { function validateActivity (object) {
if (object && object.id && object.actor) { if (object && object.id && object.actor) {
return true return true
} }
} }
module.exports.activity = function activity (req, res, next) { module.exports.activity = function activity (req, res, next) {
// TODO real validation // TODO real validation
if (!validateActivity(req.body)) { if (!validateActivity(req.body)) {
return res.status(400).send('Invalid activity') return res.status(400).send('Invalid activity')
} }
next() next()
} }
module.exports.outboxActivity = function outboxActivity (req, res, next) { module.exports.outboxActivity = function outboxActivity (req, res, next) {
if (!validateActivity(req.body)) { if (!validateActivity(req.body)) {
if (!validateObject(req.body)) { if (!validateObject(req.body)) {
return res.status(400).send('Invalid activity') return res.status(400).send('Invalid activity')
}
const newID = new ObjectId()
req.body = {
_id: newID,
'@context': pub.consts.ASContext,
type: 'Create',
id: `https://${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() const newID = new ObjectId()
} req.body = {
_id: newID,
'@context': pub.consts.ASContext,
type: 'Create',
id: `https://${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()
}

View file

@ -1,7 +1,7 @@
{ {
"name": "hubbubpub", "name": "guppe",
"version": "1.0.0", "version": "0.0.1",
"description": "", "description": "Decentralized social groups with ActivityPub, NodeJS, Express, and Mongodb",
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"body-parser": "^1.18.3", "body-parser": "^1.18.3",
@ -17,6 +17,7 @@
"node": ">=10.10.0" "node": ">=10.10.0"
}, },
"devDependencies": { "devDependencies": {
"standard": "^14.3.1",
"standardjs": "^1.0.0-alpha" "standardjs": "^1.0.0-alpha"
}, },
"scripts": { "scripts": {
@ -24,5 +25,6 @@
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "Will Murphy", "author": "Will Murphy",
"license": "AGPL-3.0-or-later" "license": "AGPL-3.0-or-later",
"repository": "https://github.com/wmurphyrd/guppe"
} }

View file

@ -1,69 +1,67 @@
const crypto = require('crypto') const crypto = require('crypto')
const request = require('request-promise-native') const { promisify } = require('util')
const {promisify} = require('util')
const store = require('../store') const store = require('../store')
const federation = require('./federation')
const pubUtils = require('./utils') const pubUtils = require('./utils')
const config = require('../config.json') const config = require('../config.json')
const generateKeyPairPromise = promisify(crypto.generateKeyPair) const generateKeyPairPromise = promisify(crypto.generateKeyPair)
module.exports = { module.exports = {
createLocalActor, createLocalActor,
getOrCreateActor getOrCreateActor
} }
function createLocalActor (name, type) { function createLocalActor (name, type) {
return generateKeyPairPromise('rsa', { return generateKeyPairPromise('rsa', {
modulusLength: 4096, modulusLength: 4096,
publicKeyEncoding: { publicKeyEncoding: {
type: 'spki', type: 'spki',
format: 'pem' format: 'pem'
}, },
privateKeyEncoding: { privateKeyEncoding: {
type: 'pkcs8', type: 'pkcs8',
format: 'pem', format: 'pem'
} }
}).then(pair => { }).then(pair => {
const actorBase = pubUtils.usernameToIRI(name); const actorBase = pubUtils.usernameToIRI(name)
return { return {
_meta: { _meta: {
privateKey: pair.privateKey, privateKey: pair.privateKey
}, },
id: `${actorBase}`, id: `${actorBase}`,
"type": type, type: type,
"following": `${actorBase}/following`, following: `${actorBase}/following`,
"followers": `${actorBase}/followers`, followers: `${actorBase}/followers`,
"liked": `${actorBase}/liked`, liked: `${actorBase}/liked`,
"inbox": `${actorBase}/inbox`, inbox: `${actorBase}/inbox`,
"outbox": `${actorBase}/outbox`, outbox: `${actorBase}/outbox`,
"preferredUsername": name, preferredUsername: name,
"name": "Dummy Person", name: 'Dummy Person',
"summary": "Gotta have someone in the db", summary: 'Gotta have someone in the db',
"icon": `https://${config.DOMAIN}/f/${name}.png`, icon: `https://${config.DOMAIN}/f/${name}.png`,
publicKey: { publicKey: {
'id': `${actorBase}#main-key`, id: `${actorBase}#main-key`,
'owner': `${actorBase}`, owner: `${actorBase}`,
'publicKeyPem': pair.publicKey publicKeyPem: pair.publicKey
}, }
} }
}) })
} }
async function getOrCreateActor (preferredUsername, db, includeMeta) { async function getOrCreateActor (preferredUsername, db, includeMeta) {
const id = pubUtils.usernameToIRI(preferredUsername) const id = pubUtils.usernameToIRI(preferredUsername)
let user = await store.actor.getActor(id, db, includeMeta) let user = await store.actor.getActor(id, db, includeMeta)
if (user) { 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._id
if (includeMeta !== true) {
delete user._meta
}
return 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._id
if (includeMeta !== true) {
delete user._meta
}
return user
}

View file

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

View file

@ -3,13 +3,13 @@ const request = require('request-promise-native')
// federation communication utilities // federation communication utilities
module.exports = { module.exports = {
requestObject, requestObject
} }
function requestObject (id) { function requestObject (id) {
return request({ return request({
url: id, url: id,
headers: {Accept: 'application/activity+json'}, headers: { Accept: 'application/activity+json' },
json: true, json: true
}) })
} }

View file

@ -1,9 +1,9 @@
'use strict'; 'use strict'
// ActivityPub / ActivityStreams utils // ActivityPub / ActivityStreams utils
module.exports = { module.exports = {
actor: require('./actor'), actor: require('./actor'),
consts: require('./consts'), consts: require('./consts'),
federation: require('./federation'), federation: require('./federation'),
object: require('./object'), object: require('./object'),
utils: require('./utils'), utils: require('./utils')
} }

View file

@ -1,16 +1,17 @@
'use strict' 'use strict'
const store = require('../store') const store = require('../store')
const federation = require('./federation')
module.exports = { module.exports = {
resolveObject resolveObject
} }
// find object in local DB or fetch from origin server // find object in local DB or fetch from origin server
async function resolveObject (id, db) { async function resolveObject (id, db) {
let object = await store.object.get(id, db) let object = await store.object.get(id, db)
if (object) { if (object) {
return object
}
object = await federation.requestObject(id)
await store.object.save(object)
return object return object
} }
object = await federation.requestObject(id)
await store.object.save(object, db)
return object
}

View file

@ -3,37 +3,36 @@ const config = require('../config.json')
const consts = require('./consts') const consts = require('./consts')
module.exports = { module.exports = {
usernameToIRI, usernameToIRI,
toJSONLD, toJSONLD,
arrayToCollection, arrayToCollection,
actorFromActivity, actorFromActivity
} }
function actorFromActivity (activity) { function actorFromActivity (activity) {
if (Object.prototype.toString.call(activity.actor) === '[object String]') { if (Object.prototype.toString.call(activity.actor) === '[object String]') {
return activity.actor return activity.actor
} }
if (activity.actor.type === 'Link') { if (activity.actor.type === 'Link') {
return activity.actor.href return activity.actor.href
} }
return activity.actor.id return activity.actor.id
} }
function arrayToCollection (arr, ordered) { function arrayToCollection (arr, ordered) {
return {
return { '@context': consts.ASContext,
'@context': consts.ASContext, totalItems: arr.length,
totalItems: arr.length, type: ordered ? 'orderedCollection' : 'collection',
type: ordered ? 'orderedCollection' : 'collection', [ordered ? 'orderedItems' : 'items']: arr
[ordered ? 'orderedItems' : 'items']: arr, }
}
} }
function toJSONLD (obj) { function toJSONLD (obj) {
obj['@context'] = obj['@context'] || consts.ASContext; obj['@context'] = obj['@context'] || consts.ASContext
return obj; return obj
} }
function usernameToIRI (user) { function usernameToIRI (user) {
return `https://${config.DOMAIN}/u/${user}` return `https://${config.DOMAIN}/u/${user}`
} }

View file

@ -1,19 +1,15 @@
const express = require('express') const express = require('express')
const router = express.Router() const router = express.Router()
const utils = require('../utils')
const pub = require('../pub') const pub = require('../pub')
const net = require('../net') const net = require('../net')
const store = require('../store')
const request = require('request-promise-native') const request = require('request-promise-native')
const httpSignature = require('http-signature') const { ObjectId } = require('mongodb')
const {ObjectId} = require('mongodb')
router.post('/', net.validators.activity, net.security.verifySignature, function (req, res) { router.post('/', net.validators.activity, net.security.verifySignature, function (req, res) {
const db = req.app.get('db'); const db = req.app.get('db')
let outgoingResponse req.body._meta = { _target: pub.utils.usernameToIRI(req.user) }
req.body._meta = {_target: pub.utils.usernameToIRI(req.user)}
// side effects // side effects
switch(req.body.type) { switch (req.body.type) {
case 'Accept': case 'Accept':
// TODO - side effect ncessary for following collection? // TODO - side effect ncessary for following collection?
break break
@ -22,7 +18,7 @@ router.post('/', net.validators.activity, net.security.verifySignature, function
// send acceptance reply // send acceptance reply
Promise.all([ Promise.all([
pub.actor.getOrCreateActor(req.user, db, true), pub.actor.getOrCreateActor(req.user, db, true),
pub.object.resolveObject(pub.utils.actorFromActivity(req.body), db), pub.object.resolveObject(pub.utils.actorFromActivity(req.body), db)
]) ])
.then(([user, actor]) => { .then(([user, actor]) => {
if (!actor || !actor.inbox) { if (!actor || !actor.inbox) {
@ -33,12 +29,12 @@ router.post('/', net.validators.activity, net.security.verifySignature, function
method: 'POST', method: 'POST',
url: actor.inbox, url: actor.inbox,
headers: { headers: {
'Content-Type': 'application/activity+json', 'Content-Type': 'application/activity+json'
}, },
httpSignature: { httpSignature: {
key: user._meta.privateKey, key: user._meta.privateKey,
keyId: user.id, keyId: user.id,
headers: ['(request-target)', 'host', 'date'], headers: ['(request-target)', 'host', 'date']
}, },
json: true, json: true,
body: pub.utils.toJSONLD({ body: pub.utils.toJSONLD({
@ -46,8 +42,8 @@ router.post('/', net.validators.activity, net.security.verifySignature, function
type: 'Accept', type: 'Accept',
id: `https://${req.app.get('domain')}/o/${newID.toHexString()}`, id: `https://${req.app.get('domain')}/o/${newID.toHexString()}`,
actor: user.id, actor: user.id,
object: req.body, object: req.body
}), })
} }
return request(responseOpts) return request(responseOpts)
}) })
@ -63,21 +59,20 @@ router.post('/', net.validators.activity, net.security.verifySignature, function
console.log(err) console.log(err)
res.status(500).send() res.status(500).send()
}) })
}); })
router.get('/', 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({'_meta._target': pub.utils.usernameToIRI(req.user)}) .find({ '_meta._target': pub.utils.usernameToIRI(req.user) })
.sort({_id: -1}) .sort({ _id: -1 })
.project({_id: 0, _meta: 0, '@context': 0, 'object._id': 0, 'object.@context': 0, 'object._meta': 0}) .project({ _id: 0, _meta: 0, '@context': 0, 'object._id': 0, 'object.@context': 0, 'object._meta': 0 })
.toArray() .toArray()
.then(stream => res.json(pub.utils.arrayToCollection(stream, true))) .then(stream => res.json(pub.utils.arrayToCollection(stream, true)))
.catch(err => { .catch(err => {
console.log(err) console.log(err)
return res.status(500).send() return res.status(500).send()
}) })
;
}) })
module.exports = router; module.exports = router

View file

@ -1,9 +1,9 @@
'use strict'; 'use strict'
module.exports = { module.exports = {
user: require('./user'), user: require('./user'),
message: require('./message'), message: require('./message'),
inbox: require('./inbox'), inbox: require('./inbox'),
outbox: require('./outbox'), outbox: require('./outbox'),
webfinger: require('./webfinger'), webfinger: require('./webfinger')
}; }

View file

@ -1,22 +1,20 @@
'use strict'; 'use strict'
const express = require('express'), const express = require('express')
router = express.Router(); const router = express.Router()
router.get('/:guid', function (req, res) { router.get('/:guid', function (req, res) {
let guid = req.params.guid; const guid = req.params.guid
if (!guid) { if (!guid) {
return res.status(400).send('Bad request.'); return res.status(400).send('Bad request.')
} } else {
else { const db = req.app.get('db')
let db = req.app.get('db'); const result = db.prepare('select message from messages where guid = ?').get(guid)
let result = db.prepare('select message from messages where guid = ?').get(guid);
if (result === undefined) { if (result === undefined) {
return res.status(404).send(`No record found for ${guid}.`); return res.status(404).send(`No record found for ${guid}.`)
} } else {
else { res.json(JSON.parse(result.message))
res.json(JSON.parse(result.message));
} }
} }
}); })
module.exports = router; module.exports = router

View file

@ -1,11 +1,10 @@
const express = require('express') const express = require('express')
const router = express.Router() const router = express.Router()
const utils = require('../utils')
const net = require('../net') const net = require('../net')
const pub = require('../pub') const pub = require('../pub')
router.post('/', net.validators.outboxActivity, function (req, res) { router.post('/', net.validators.outboxActivity, function (req, res) {
const db = req.app.get('db'); const db = req.app.get('db')
Promise.all([ Promise.all([
db.collection('objects').insertOne(req.body.object), db.collection('objects').insertOne(req.body.object),
db.collection('streams').insertOne(req.body) db.collection('streams').insertOne(req.body)
@ -14,21 +13,20 @@ router.post('/', net.validators.outboxActivity, function (req, res) {
console.log(err) console.log(err)
res.status(500).send() res.status(500).send()
}) })
}); })
router.get('/', 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({actor: pub.utils.usernameToIRI(req.user)}) .find({ actor: pub.utils.usernameToIRI(req.user) })
.sort({_id: -1}) .sort({ _id: -1 })
.project({_id: 0, _meta: 0, 'object._id': 0, 'object.@context': 0, 'object._meta': 0}) .project({ _id: 0, _meta: 0, 'object._id': 0, 'object.@context': 0, 'object._meta': 0 })
.toArray() .toArray()
.then(stream => res.json(pub.utils.arrayToCollection(stream, true))) .then(stream => res.json(pub.utils.arrayToCollection(stream, true)))
.catch(err => { .catch(err => {
console.log(err) console.log(err)
return res.status(500).send() return res.status(500).send()
}) })
;
}) })
module.exports = router; module.exports = router

View file

@ -1,37 +1,34 @@
'use strict' 'use strict'
const express = require('express') const express = require('express')
const router = express.Router() const router = express.Router()
const utils = require('../utils')
const pub = require('../pub') const pub = require('../pub')
const store = require('../store')
router.get('/:name', async function (req, res) { router.get('/:name', async function (req, res) {
let name = req.params.name; const name = req.params.name
if (!name) { if (!name) {
return res.status(400).send('Bad request.'); return res.status(400).send('Bad request.')
} } else {
else { const db = req.app.get('db')
let db = req.app.get('db')
const user = await pub.actor.getOrCreateActor(name, db) const user = await pub.actor.getOrCreateActor(name, db)
if (user) { if (user) {
return res.json(pub.utils.toJSONLD(user)) return res.json(pub.utils.toJSONLD(user))
} }
return res.status(404).send('Person not found') return res.status(404).send('Person not found')
} }
}); })
router.get('/:name/followers', function (req, res) { router.get('/:name/followers', function (req, res) {
let name = req.params.name; const name = req.params.name
if (!name) { if (!name) {
return res.status(400).send('Bad request.'); return res.status(400).send('Bad request.')
} }
const db = req.app.get('db') const db = req.app.get('db')
db.collection('streams') db.collection('streams')
.find({ .find({
type: 'Follow', type: 'Follow',
'_meta._target': pub.utils.usernameToIRI(name), '_meta._target': pub.utils.usernameToIRI(name)
}) })
.project({_id: 0, actor: 1}) .project({ _id: 0, actor: 1 })
.toArray() .toArray()
.then(follows => { .then(follows => {
const followers = follows.map(pub.utils.actorFromActivity) const followers = follows.map(pub.utils.actorFromActivity)
@ -41,6 +38,6 @@ router.get('/:name/followers', function (req, res) {
console.log(err) console.log(err)
return res.status(500).send() return res.status(500).send()
}) })
}); })
module.exports = router; module.exports = router

View file

@ -5,36 +5,36 @@ const router = express.Router()
const pub = require('../pub') const pub = require('../pub')
const acctReg = /acct:[@~]?([^@]+)@?(.*)/ const acctReg = /acct:[@~]?([^@]+)@?(.*)/
router.get('/', function (req, res) { router.get('/', function (req, res) {
let resource = req.query.resource; const resource = req.query.resource
let acct = acctReg.exec(resource) const acct = acctReg.exec(resource)
if (!acct || acct.length < 2) { 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.')
} }
if (acct[2] && acct[2].toLowerCase() !== req.app.get('domain').toLowerCase()) { if (acct[2] && acct[2].toLowerCase() !== req.app.get('domain').toLowerCase()) {
return res.status(400).send('Requested user is not from this domain') return res.status(400).send('Requested user is not from this domain')
} }
let db = req.app.get('db'); const db = req.app.get('db')
pub.actor.getOrCreateActor(acct[1], db) pub.actor.getOrCreateActor(acct[1], db)
.then(result => { .then(result => {
if (!result) { if (!result) {
return res.status(404).send(`${acct[1]}@${acct[2]} not found`) return res.status(404).send(`${acct[1]}@${acct[2]} not found`)
} }
const finger = { const finger = {
'subject': resource, subject: resource,
'links': [ links: [
{ {
'rel': 'self', rel: 'self',
'type': 'application/activity+json', type: 'application/activity+json',
'href': result.id href: result.id
} }
] ]
} }
return res.json(finger) return res.json(finger)
}) })
.catch(err => { .catch(err => {
console.log(err); console.log(err)
res.status(500).send() res.status(500).send()
}) })
}); })
module.exports = router; module.exports = router

View file

@ -1,15 +1,15 @@
'use strict' 'use strict'
module.exports = { module.exports = {
getActor, getActor
} }
const actorProj = {_id: 0, _meta: 0} const actorProj = { _id: 0, _meta: 0 }
const metaActorProj = {_id: 0} const metaActorProj = { _id: 0 }
function getActor (id, db, includeMeta) { function getActor (id, db, includeMeta) {
return db.collection('objects') return db.collection('objects')
.find({id: id}) .find({ id: id })
.limit(1) .limit(1)
// strict comparison as we don't want to return private keys on accident // strict comparison as we don't want to return private keys on accident
.project(includeMeta === true ? metaActorProj : actorProj) .project(includeMeta === true ? metaActorProj : actorProj)

View file

@ -3,6 +3,6 @@
module.exports = { module.exports = {
setup: require('./setup'), setup: require('./setup'),
actor: require('./actor'), actor: require('./actor'),
object: require('./object'), object: require('./object')
// stream: require('./stream'), // stream: require('./stream'),
} }

View file

@ -1,19 +1,19 @@
'use strict' 'use strict'
module.exports = { module.exports = {
get, get,
save, save
} }
function get (id, db) { function get (id, db) {
return db.collection('objects') return db.collection('objects')
.find({id: id}) .find({ id: id })
.limit(1) .limit(1)
.project({_id: 0, _meta: 0}) .project({ _id: 0, _meta: 0 })
.next() .next()
} }
function save (object) { function save (object, db) {
return db.collection('objects') return db.collection('objects')
.insertOne(object) .insertOne(object)
} }

View file

@ -1,37 +1,37 @@
'use strict' 'use strict'
module.exports = async function dbSetup (db, domain, dummyUser) { module.exports = async function dbSetup (db, domain, dummyUser) {
// inbox // inbox
await db.collection('streams').createIndex({ await db.collection('streams').createIndex({
'_meta._target': 1, '_meta._target': 1,
_id: -1, _id: -1
}, { }, {
name: 'inbox' name: 'inbox'
}) })
// followers // followers
await db.collection('streams').createIndex({ await db.collection('streams').createIndex({
'_meta._target': 1, '_meta._target': 1
}, { }, {
partialFilterExpression: {type: 'Follow'}, partialFilterExpression: { type: 'Follow' },
name: 'followers' name: 'followers'
}) })
// outbox // outbox
await db.collection('streams').createIndex({ await db.collection('streams').createIndex({
actor: 1, actor: 1,
_id: -1, _id: -1
}) })
// object lookup // object lookup
await db.collection('objects').createIndex({ await db.collection('objects').createIndex({
id: 1 id: 1
}) })
if (dummyUser) { if (dummyUser) {
return db.collection('objects').findOneAndReplace( return db.collection('objects').findOneAndReplace(
{preferredUsername: 'dummy'}, { preferredUsername: 'dummy' },
dummyUser, dummyUser,
{ {
upsert: true, upsert: true,
returnOriginal: false, returnOriginal: false
} }
) )
} }
} }

View file

@ -1,5 +1,5 @@
'use strict' 'use strict'
// misc utilities // misc utilities
module.exports = { module.exports = {
consts: require('./consts') consts: require('./consts')
} }