cleaning federation

This commit is contained in:
lesion
2019-07-30 18:32:26 +02:00
parent 84ac387626
commit a7c4592a35
10 changed files with 253 additions and 180 deletions

View File

@@ -0,0 +1,28 @@
const config = require('config')
const Helpers = require('./helpers')
const { user: User } = require('../api/models')
module.exports = {
// follow request from fediverse
async follow (req, res, body, targetOrigin, domain) {
if (typeof b.object !== 'string') return
const username = body.object.replace(`${config.baseurl}/federation/u/`, '')
console.error('someone wants to follow ' + username)
const user = await User.findOne({ where: { username }})
if (!user) {
console.error('No user found!')
return
}
Helpers.sendAcceptMessage(body, user, domain, req, res, targetOrigin)
console.error('FOLLOWERS ', user.followers)
if (user.followers.indexOf(body.actor) === -1) {
console.error('ok this is a new follower: ', body.actor)
await user.update({ followers: [...user.followers, body.actor] })
}
},
// unfollow request from fediverse
unfollow () {
console.error('inside unfollow')
}
}

View File

@@ -0,0 +1,54 @@
const fetch = require('fetch')
const request = require('request')
const crypto = require('crypto')
const Helpers = {
async signAndSend(message, user, domain, req, res, targetOrigin) {
// get the URI of the actor object and append 'inbox' to it
let inbox = message.object.actor+'/inbox'
let inboxFragment = inbox.replace(targetOrigin,'')
const targetDomain = new URL(targetOrigin).host
// get the private key
const privkey = user.rsa.privateKey
const signer = crypto.createSign('sha256')
let d = new Date()
let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}`
signer.update(stringToSign)
signer.end()
const signature = signer.sign(privkey)
const signature_b64 = signature.toString('base64')
let header = `keyId="${config.baseurl}/federation/u/${user.username}",headers="(request-target) host date",signature="${signature_b64}"`
request({
url: inbox,
headers: {
'Host': targetDomain,
'Date': d.toUTCString(),
'Signature': header
},
method: 'POST',
json: true,
body: message
}, function (error, response){
if (error) {
console.log('Error:', error, response.body)
}
else {
console.log('Response:', response.body)
}
})
return res.status(200)
},
async sendAcceptMessage (body, user, domain, req, res, targetOrigin) {
const guid = crypto.randomBytes(16).toString('hex')
let message = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `${config.baseurl}/federation/${guid}`,
'type': 'Accept',
'actor': `${config.baseurl}/federation/u/${user.username}`,
'object': body,
}
Helpers.signAndSend(message, user, domain, req, res, targetOrigin)
}
}
module.exports = Helpers

View File

@@ -1,130 +1,47 @@
const express = require('express')
const router = express.Router()
const { user: User } = require('../api/models')
const config = require('config')
const get = require('lodash/get')
const crypto = require('crypto')
const request = require('request')
const bodyParser = require('body-parser')
const cors = require('cors')
const Follows = require('./follows')
const Users = require('./users')
function signAndSend(message, user, domain, req, res, targetOrigin) {
// get the URI of the actor object and append 'inbox' to it
let inbox = message.object.actor+'/inbox'
let inboxFragment = inbox.replace(targetOrigin,'')
const targetDomain = new URL(targetOrigin).host
// get the private key
const privkey = user.rsa.privateKey
const signer = crypto.createSign('sha256')
let d = new Date()
let stringToSign = `(request-target): post ${inboxFragment}\nhost: ${targetDomain}\ndate: ${d.toUTCString()}`
signer.update(stringToSign)
signer.end()
const signature = signer.sign(privkey)
const signature_b64 = signature.toString('base64')
let header = `keyId="${config.baseurl}/federation/u/${user.username}",headers="(request-target) host date",signature="${signature_b64}"`
request({
url: inbox,
headers: {
'Host': targetDomain,
'Date': d.toUTCString(),
'Signature': header
},
method: 'POST',
json: true,
body: message
}, function (error, response){
if (error) {
console.log('Error:', error, response.body)
}
else {
console.log('Response:', response.body)
}
})
return res.status(200);
}
/**
* Federation is calling!
* ref: https://www.w3.org/TR/activitypub/#Overview
*/
router.use(cors())
router.use(bodyParser.json({type: 'application/activity+json'}))
function sendAcceptMessage (body, user, domain, req, res, targetOrigin) {
const guid = crypto.randomBytes(16).toString('hex')
let message = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': `${config.baseurl}/federation/${guid}`,
'type': 'Accept',
'actor': `${config.baseurl}/federation/u/${user.username}`,
'object': body,
}
signAndSend(message, user, domain, req, res, targetOrigin)
}
router.post('/inbox', async (req, res) => {
// get any message coming from federation
// Federation is calling!
router.post('/u/:name/inbox', async (req, res) => {
const b = req.body
console.error('> INBOX ', b)
console.error('> INBOX ', b.type, b)
const targetOrigin = new URL(b.actor).origin
const domain = new URL(config.baseurl).host
switch(b.type) {
case 'Follow':
if (typeof b.object !== 'string') return
const username = b.object.replace(`${config.baseurl}/federation/u/`, '')
console.error('someone wants to follow ' + username)
const user = await User.findOne({ where: { username }})
if (!user) {
console.error('No user found!')
return
}
sendAcceptMessage(b, user, domain, req, res, targetOrigin)
console.error('FOLLOWERS ', user.followers)
if (user.followers.indexOf(b.actor) === -1) {
console.error('ok this is a new follower: ', b.actor)
await user.update({ followers: [...user.followers, b.actor] })
}
Follows.follow(req, res, b, targetOrigin, domain)
break
case 'Undo':
Follows.unfollow(req, res, b, targetOrigin, domain)
break
case 'Announce':
console.error('This is a boost ?')
break
case 'Note':
console.error('this is a note ! I should not receive this')
break
case 'Create':
console.error('Create what? This is probably a reply', b.object.type)
break
}
})
router.get('/u/:name', async (req, res) => {
const name = req.params.name
if (!name) return res.status(400).send('Bad request.')
const user = await User.findOne({where: { username: name }})
if (!user) return res.status(404).send(`No record found for ${name}`)
const ret = {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
id: `${config.baseurl}/federation/u/${name}`,
type: 'Person',
preferredUsername: name,
nodeInfo2Url: `${config.baseurl}/.well-known/x-nodeinfo2`,
inbox: `${config.baseurl}/federation/inbox`,
followers: `${config.baseurl}/federation/u/${name}/followers`,
publicKey: {
id: `${config.baseurl}/federation/u/${name}#main-key`,
owner: `${config.baseurl}/federation/u/${name}`,
publicKeyPem: get(user, 'rsa.publicKey', '')
}
}
res.json(ret)
})
router.get('/u/:name/followers', async (req, res) => {
const name = req.params.name
if (!name) return res.status(400).send('Bad request.')
const user = await User.findOne({where: { username: name }})
if (!user) return res.status(404).send(`No record found for ${name}`)
const ret = {
'@context': [ 'https://www.w3.org/ns/activitystreams' ],
id: `${config.baseurl}/federation/u/${name}/followers`,
type: 'OrderedCollection',
totalItems: user.followers.length,
first: {
id: `${config.baseurl}/federation/u/${name}/followers?page=1`,
type: 'OrderedCollectionPage',
totalItems: user.followers.length,
partOf: `${config.baseurl}/federation/u/${name}/followers`,
orderedItems: user.followers,
}
}
res.json(ret)
})
router.get('/u/:name/outbox', Users.outbox)
router.get('/u/:name/followers', Users.followers)
router.get('/u/:name', Users.get)
module.exports = router

View File

@@ -0,0 +1,53 @@
const { user: User } = require('../api/models')
const config = require('config')
module.exports = {
async get (req, res) {
const name = req.params.name
if (!name) return res.status(400).send('Bad request.')
const user = await User.findOne({where: { username: name }})
if (!user) return res.status(404).send(`No record found for ${name}`)
const ret = {
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
id: `${config.baseurl}/federation/u/${name}`,
type: 'Person',
preferredUsername: name,
nodeInfo2Url: `${config.baseurl}/.well-known/x-nodeinfo2`,
inbox: `${config.baseurl}/federation/u/${name}/inbox`,
outbox: `${config.baseurl}/federation/u/${name}/outbox`,
followers: `${config.baseurl}/federation/u/${name}/followers`,
publicKey: {
id: `${config.baseurl}/federation/u/${name}#main-key`,
owner: `${config.baseurl}/federation/u/${name}`,
publicKeyPem: get(user, 'rsa.publicKey', '')
}
}
res.json(ret)
},
async followers (req, res) {
const name = req.params.name
if (!name) return res.status(400).send('Bad request.')
const user = await User.findOne({where: { username: name }})
if (!user) return res.status(404).send(`No record found for ${name}`)
const ret = {
'@context': [ 'https://www.w3.org/ns/activitystreams' ],
id: `${config.baseurl}/federation/u/${name}/followers`,
type: 'OrderedCollection',
totalItems: user.followers.length,
first: {
id: `${config.baseurl}/federation/u/${name}/followers?page=1`,
type: 'OrderedCollectionPage',
totalItems: user.followers.length,
partOf: `${config.baseurl}/federation/u/${name}/followers`,
orderedItems: user.followers,
}
}
res.json(ret)
},
outbox (req, res) {
console.error('Inside outbox, should return all events from this user')
}
}

View File

@@ -3,7 +3,6 @@ const path = require('path')
const express = require('express')
const consola = require('consola')
const morgan = require('morgan')
const bodyParser = require('body-parser')
const cors = require('cors')
const { Nuxt, Builder } = require('nuxt')
@@ -37,7 +36,7 @@ async function start() {
// federation api / activitypub / webfinger / nodeinfo
app.use('/.well-known/webfinger', cors(), require('./federation/webfinger'))
app.use('/.well-known/x-nodeinfo2', cors(), require('./federation/nodeinfo'))
app.use('/federation', cors(), bodyParser.join({type: 'applicatoin/activity+json'}), require('./federation'))
app.use('/federation', require('./federation'))
// Give nuxt middleware to express
app.use(nuxt.render)