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 express = require('express');
const MongoClient = require('mongodb').MongoClient;
const fs = require('fs');
const express = require('express')
const MongoClient = require('mongodb').MongoClient
const fs = require('fs')
const bodyParser = require('body-parser')
const cors = require('cors')
const http = require('http')
const https = require('https')
const basicAuth = require('express-basic-auth');
const routes = require('./routes')
const pub = require('./pub')
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
const url = 'mongodb://localhost:27017';
const url = 'mongodb://localhost:27017'
const store = require('./store');
const store = require('./store')
// Database Name
const dbName = 'test';
const dbName = 'test'
// Create a new MongoClient
const client = new MongoClient(url, {useUnifiedTopology: true});
const client = new MongoClient(url, { useUnifiedTopology: true })
let db;
let db
let sslOptions;
sslOptions = {
const 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: [
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);
}
}
]
})) // support json encoded bodies
app.use(bodyParser.urlencoded({ extended: true })) // support encoded bodies
app.param('name', function (req, res, next, id) {
req.user = id
next()
})
app.get('/', (req, res) => res.send('Hello World!'));
app.get('/', (req, res) => res.send('Hello World!'))
// admin page
app.use('/.well-known/webfinger', cors(), routes.webfinger);
app.use('/u', cors(), routes.user);
app.use('/m', cors(), routes.message);
app.use('/.well-known/webfinger', cors(), routes.webfinger)
app.use('/u', cors(), routes.user)
app.use('/m', cors(), routes.message)
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('/admin', express.static('public/admin'))
app.use('/f', express.static('public/files'))
// app.use('/hubs', express.static('../hubs/dist'));
// Use connect method to connect to the Server
client.connect({useNewUrlParser: true})
client.connect({ useNewUrlParser: true })
.then(() => {
console.log("Connected successfully to server");
db = client.db(dbName);
app.set('db', db);
console.log('Connected successfully to server')
db = client.db(dbName)
app.set('db', db)
return pub.actor.createLocalActor('dummy', 'Person')
})
.then(dummy => {
@ -95,9 +70,9 @@ client.connect({useNewUrlParser: true})
})
.then(() => {
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 => {
throw new Error(err)
});
})

View file

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

View file

@ -3,7 +3,7 @@ const httpSignature = require('http-signature')
const pub = require('../pub')
// http communication middleware
module.exports = {
verifySignature,
verifySignature
}
async function verifySignature (req, res, next) {

View file

@ -1,4 +1,4 @@
const {ObjectId} = require('mongodb')
const { ObjectId } = require('mongodb')
// const activities = ['Create', ]
const pub = require('../pub')
@ -40,7 +40,7 @@ module.exports.outboxActivity = function outboxActivity (req, res, next) {
to: req.body.to,
cc: req.body.cc,
bcc: req.body.cc,
audience: req.body.audience,
audience: req.body.audience
}
}
next()

View file

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

View file

@ -1,9 +1,7 @@
const crypto = require('crypto')
const request = require('request-promise-native')
const {promisify} = require('util')
const { promisify } = require('util')
const store = require('../store')
const federation = require('./federation')
const pubUtils = require('./utils')
const config = require('../config.json')
@ -23,30 +21,30 @@ function createLocalActor (name, type) {
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
format: 'pem'
}
}).then(pair => {
const actorBase = pubUtils.usernameToIRI(name);
const actorBase = pubUtils.usernameToIRI(name)
return {
_meta: {
privateKey: pair.privateKey,
privateKey: pair.privateKey
},
id: `${actorBase}`,
"type": type,
"following": `${actorBase}/following`,
"followers": `${actorBase}/followers`,
"liked": `${actorBase}/liked`,
"inbox": `${actorBase}/inbox`,
"outbox": `${actorBase}/outbox`,
"preferredUsername": name,
"name": "Dummy Person",
"summary": "Gotta have someone in the db",
"icon": `https://${config.DOMAIN}/f/${name}.png`,
type: type,
following: `${actorBase}/following`,
followers: `${actorBase}/followers`,
liked: `${actorBase}/liked`,
inbox: `${actorBase}/inbox`,
outbox: `${actorBase}/outbox`,
preferredUsername: name,
name: 'Dummy Person',
summary: 'Gotta have someone in the db',
icon: `https://${config.DOMAIN}/f/${name}.png`,
publicKey: {
'id': `${actorBase}#main-key`,
'owner': `${actorBase}`,
'publicKeyPem': pair.publicKey
},
id: `${actorBase}#main-key`,
owner: `${actorBase}`,
publicKeyPem: pair.publicKey
}
}
})
}

View file

@ -1,3 +1,3 @@
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
module.exports = {
requestObject,
requestObject
}
function requestObject (id) {
return request({
url: id,
headers: {Accept: 'application/activity+json'},
json: true,
headers: { Accept: 'application/activity+json' },
json: true
})
}

View file

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

View file

@ -1,5 +1,6 @@
'use strict'
const store = require('../store')
const federation = require('./federation')
module.exports = {
resolveObject
}
@ -11,6 +12,6 @@ async function resolveObject (id, db) {
return object
}
object = await federation.requestObject(id)
await store.object.save(object)
await store.object.save(object, db)
return object
}

View file

@ -6,7 +6,7 @@ module.exports = {
usernameToIRI,
toJSONLD,
arrayToCollection,
actorFromActivity,
actorFromActivity
}
function actorFromActivity (activity) {
@ -20,18 +20,17 @@ function actorFromActivity (activity) {
}
function arrayToCollection (arr, ordered) {
return {
'@context': consts.ASContext,
totalItems: arr.length,
type: ordered ? 'orderedCollection' : 'collection',
[ordered ? 'orderedItems' : 'items']: arr,
[ordered ? 'orderedItems' : 'items']: arr
}
}
function toJSONLD (obj) {
obj['@context'] = obj['@context'] || consts.ASContext;
return obj;
obj['@context'] = obj['@context'] || consts.ASContext
return obj
}
function usernameToIRI (user) {

View file

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

View file

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

View file

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

View file

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

View file

@ -1,37 +1,34 @@
'use strict'
const express = require('express')
const router = express.Router()
const utils = require('../utils')
const pub = require('../pub')
const store = require('../store')
router.get('/:name', async function (req, res) {
let name = req.params.name;
const name = req.params.name
if (!name) {
return res.status(400).send('Bad request.');
}
else {
let db = req.app.get('db')
return res.status(400).send('Bad request.')
} else {
const db = req.app.get('db')
const user = await pub.actor.getOrCreateActor(name, db)
if (user) {
return res.json(pub.utils.toJSONLD(user))
}
return res.status(404).send('Person not found')
}
});
})
router.get('/:name/followers', function (req, res) {
let name = req.params.name;
const name = req.params.name
if (!name) {
return res.status(400).send('Bad request.');
return res.status(400).send('Bad request.')
}
const db = req.app.get('db')
db.collection('streams')
.find({
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()
.then(follows => {
const followers = follows.map(pub.utils.actorFromActivity)
@ -41,6 +38,6 @@ router.get('/:name/followers', function (req, res) {
console.log(err)
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 acctReg = /acct:[@~]?([^@]+)@?(.*)/
router.get('/', function (req, res) {
let resource = req.query.resource;
let acct = acctReg.exec(resource)
const resource = req.query.resource
const 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.')
}
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 db = req.app.get('db')
pub.actor.getOrCreateActor(acct[1], db)
.then(result => {
if (!result) {
return res.status(404).send(`${acct[1]}@${acct[2]} not found`)
}
const finger = {
'subject': resource,
'links': [
subject: resource,
links: [
{
'rel': 'self',
'type': 'application/activity+json',
'href': result.id
rel: 'self',
type: 'application/activity+json',
href: result.id
}
]
}
return res.json(finger)
})
.catch(err => {
console.log(err);
console.log(err)
res.status(500).send()
})
});
})
module.exports = router;
module.exports = router

View file

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

View file

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

View file

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

View file

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