package details, lint
This commit is contained in:
parent
03b0ae732e
commit
a8230136f8
22 changed files with 302 additions and 339 deletions
99
index.js
99
index.js
|
@ -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)
|
||||||
});
|
})
|
||||||
|
|
|
@ -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')
|
||||||
};
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
10
package.json
10
package.json
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
104
pub/actor.js
104
pub/actor.js
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ASContext: 'https://www.w3.org/ns/activitystreams',
|
ASContext: 'https://www.w3.org/ns/activitystreams'
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
12
pub/index.js
12
pub/index.js
|
@ -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')
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
41
pub/utils.js
41
pub/utils.js
|
@ -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}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
};
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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'),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
// misc utilities
|
// misc utilities
|
||||||
module.exports = {
|
module.exports = {
|
||||||
consts: require('./consts')
|
consts: require('./consts')
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue