organize
This commit is contained in:
parent
4d7e99461d
commit
8039b04799
20 changed files with 147 additions and 293 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,3 +3,5 @@ package-lock.json
|
||||||
*.db
|
*.db
|
||||||
config.json
|
config.json
|
||||||
public/files
|
public/files
|
||||||
|
certs/
|
||||||
|
.vscode
|
||||||
|
|
5
db/actor.js
Normal file
5
db/actor.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
}
|
7
db/index.js
Normal file
7
db/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
// database interface
|
||||||
|
module.exports = {
|
||||||
|
setup: require('./setup'),
|
||||||
|
actor: require('./actor'),
|
||||||
|
// stream: require('./stream'),
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
const utils = require('../utils')
|
const pub = require('../pub')
|
||||||
const crypto = require('crypto')
|
|
||||||
|
|
||||||
module.exports = async function dbSetup (db, domain) {
|
module.exports = async function dbSetup (db, domain) {
|
||||||
// inbox
|
// inbox
|
||||||
|
@ -25,7 +24,7 @@ module.exports = async function dbSetup (db, domain) {
|
||||||
await db.collection('objects').createIndex({
|
await db.collection('objects').createIndex({
|
||||||
id: 1
|
id: 1
|
||||||
})
|
})
|
||||||
const dummyUser = await utils.createLocalActor('dummy', 'Person')
|
const dummyUser = await pub.actor.createLocalActor('dummy', 'Person')
|
||||||
await db.collection('objects').findOneAndReplace(
|
await db.collection('objects').findOneAndReplace(
|
||||||
{preferredUsername: 'dummy'},
|
{preferredUsername: 'dummy'},
|
||||||
dummyUser,
|
dummyUser,
|
||||||
|
|
3
index.js
3
index.js
|
@ -72,9 +72,6 @@ app.param('name', function (req, res, next, id) {
|
||||||
app.get('/', (req, res) => res.send('Hello World!'));
|
app.get('/', (req, res) => res.send('Hello World!'));
|
||||||
|
|
||||||
// admin page
|
// admin page
|
||||||
app.options('/api', cors());
|
|
||||||
app.use('/api', cors(), routes.api);
|
|
||||||
app.use('/api/admin', cors({ credentials: true, origin: true }), basicUserAuth, routes.admin);
|
|
||||||
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);
|
||||||
|
|
6
net/index.js
Normal file
6
net/index.js
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
'use strict';
|
||||||
|
// middleware and networking utils
|
||||||
|
module.exports = {
|
||||||
|
validators: require('./validators'),
|
||||||
|
// comms: require('./comms'),
|
||||||
|
};
|
|
@ -1,10 +1,10 @@
|
||||||
const {ObjectId} = require('mongodb')
|
const {ObjectId} = require('mongodb')
|
||||||
// const activities = ['Create', ]
|
// const activities = ['Create', ]
|
||||||
const {ASContext} = require('./consts')
|
const pub = require('../pub')
|
||||||
|
|
||||||
function validateObject (object) {
|
function validateObject (object) {
|
||||||
if (object && object.id) {
|
if (object && object.id) {
|
||||||
object['@context'] = object['@context'] || ASContext
|
object['@context'] = object['@context'] || pub.consts.ASContext
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ module.exports.outboxActivity = function outboxActivity (req, res, next) {
|
||||||
const newID = new ObjectId()
|
const newID = new ObjectId()
|
||||||
req.body = {
|
req.body = {
|
||||||
_id: newID,
|
_id: newID,
|
||||||
'@context': ASContext,
|
'@context': pub.consts.ASContext,
|
||||||
type: 'Create',
|
type: 'Create',
|
||||||
id: `https://${req.app.get('domain')}/o/${newID.toHexString()}`,
|
id: `https://${req.app.get('domain')}/o/${newID.toHexString()}`,
|
||||||
actor: req.body.attributedTo,
|
actor: req.body.attributedTo,
|
48
pub/actor.js
Normal file
48
pub/actor.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const {promisify} = require('util')
|
||||||
|
|
||||||
|
const pubUtils = require('./utils')
|
||||||
|
const config = require('../config.json')
|
||||||
|
|
||||||
|
const generateKeyPairPromise = promisify(crypto.generateKeyPair)
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createLocalActor
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLocalActor (name, type) {
|
||||||
|
return generateKeyPairPromise('rsa', {
|
||||||
|
modulusLength: 4096,
|
||||||
|
publicKeyEncoding: {
|
||||||
|
type: 'spki',
|
||||||
|
format: 'pem'
|
||||||
|
},
|
||||||
|
privateKeyEncoding: {
|
||||||
|
type: 'pkcs8',
|
||||||
|
format: 'pem',
|
||||||
|
}
|
||||||
|
}).then(pair => {
|
||||||
|
const actorBase = pubUtils.usernameToIRI(name);
|
||||||
|
return {
|
||||||
|
_meta: {
|
||||||
|
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`,
|
||||||
|
publicKey: {
|
||||||
|
'id': `${actorBase}#main-key`,
|
||||||
|
'owner': `${actorBase}`,
|
||||||
|
'publicKeyPem': pair.publicKey
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
3
pub/consts.js
Normal file
3
pub/consts.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
ASContext: 'https://www.w3.org/ns/activitystreams',
|
||||||
|
}
|
7
pub/index.js
Normal file
7
pub/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
'use strict';
|
||||||
|
// ActivityPub / ActivityStreams utils
|
||||||
|
module.exports = {
|
||||||
|
actor: require('./actor'),
|
||||||
|
utils: require('./utils'),
|
||||||
|
consts: require('./consts'),
|
||||||
|
}
|
39
pub/utils.js
Normal file
39
pub/utils.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
'use strict'
|
||||||
|
const config = require('../config.json')
|
||||||
|
const consts = require('./consts')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
usernameToIRI,
|
||||||
|
toJSONLD,
|
||||||
|
arrayToCollection,
|
||||||
|
actorFromActivity,
|
||||||
|
}
|
||||||
|
|
||||||
|
function actorFromActivity (activity) {
|
||||||
|
if (Object.prototype.toString.call(activity.actor) === '[object String]') {
|
||||||
|
return activity.actor
|
||||||
|
}
|
||||||
|
if (activity.actor.type === 'Link') {
|
||||||
|
return activity.actor.href
|
||||||
|
}
|
||||||
|
return activity.actor.id
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayToCollection (arr, ordered) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
'@context': consts.ASContext,
|
||||||
|
totalItems: arr.length,
|
||||||
|
type: ordered ? 'orderedCollection' : 'collection',
|
||||||
|
[ordered ? 'orderedItems' : 'items']: arr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toJSONLD (obj) {
|
||||||
|
obj['@context'] = obj['@context'] || consts.ASContext;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
function usernameToIRI (user) {
|
||||||
|
return `https://${config.DOMAIN}/u/${user}`
|
||||||
|
}
|
|
@ -1,75 +0,0 @@
|
||||||
'use strict';
|
|
||||||
const express = require('express'),
|
|
||||||
router = express.Router(),
|
|
||||||
crypto = require('crypto');
|
|
||||||
|
|
||||||
function createActor(name, domain, pubkey) {
|
|
||||||
return {
|
|
||||||
'@context': [
|
|
||||||
'https://www.w3.org/ns/activitystreams',
|
|
||||||
'https://w3id.org/security/v1'
|
|
||||||
],
|
|
||||||
|
|
||||||
'id': `https://${domain}/u/${name}`,
|
|
||||||
'type': 'Person',
|
|
||||||
'preferredUsername': `${name}`,
|
|
||||||
'inbox': `https://${domain}/api/inbox`,
|
|
||||||
'followers': `https://${domain}/u/${name}/followers`,
|
|
||||||
|
|
||||||
'publicKey': {
|
|
||||||
'id': `https://${domain}/u/${name}#main-key`,
|
|
||||||
'owner': `https://${domain}/u/${name}`,
|
|
||||||
'publicKeyPem': pubkey
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createWebfinger(name, domain) {
|
|
||||||
return {
|
|
||||||
'subject': `acct:${name}@${domain}`,
|
|
||||||
|
|
||||||
'links': [
|
|
||||||
{
|
|
||||||
'rel': 'self',
|
|
||||||
'type': 'application/activity+json',
|
|
||||||
'href': `https://${domain}/u/${name}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
router.post('/create', function (req, res) {
|
|
||||||
// pass in a name for an account, if the account doesn't exist, create it!
|
|
||||||
const account = req.body.account;
|
|
||||||
if (account === undefined) {
|
|
||||||
return res.status(400).json({msg: 'Bad request. Please make sure "account" is a property in the POST body.'});
|
|
||||||
}
|
|
||||||
let db = req.app.get('db');
|
|
||||||
let domain = req.app.get('domain');
|
|
||||||
// create keypair
|
|
||||||
var pair = crypto.generateKeyPairSync('rsa', {
|
|
||||||
modulusLength: 4096,
|
|
||||||
publicKeyEncoding: {
|
|
||||||
type: 'spki',
|
|
||||||
format: 'pem'
|
|
||||||
},
|
|
||||||
privateKeyEncoding: {
|
|
||||||
type: 'pkcs8',
|
|
||||||
format: 'pem',
|
|
||||||
cipher: 'aes-256-cbc',
|
|
||||||
passphrase: 'top secret'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let actorRecord = createActor(account, domain, pair.publicKey);
|
|
||||||
let webfingerRecord = createWebfinger(account, domain);
|
|
||||||
const apikey = crypto.randomBytes(16).toString('hex');
|
|
||||||
try {
|
|
||||||
db.prepare('insert or replace into accounts(name, actor, apikey, pubkey, privkey, webfinger) values(?, ?, ?, ?, ?, ?)').run(`${account}@${domain}`, JSON.stringify(actorRecord), apikey, pair.publicKey, pair.privateKey, JSON.stringify(webfingerRecord));
|
|
||||||
res.status(200).json({msg: 'ok', apikey});
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
res.status(200).json({error: e});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
|
119
routes/api.js
119
routes/api.js
|
@ -1,119 +0,0 @@
|
||||||
'use strict';
|
|
||||||
const express = require('express'),
|
|
||||||
router = express.Router(),
|
|
||||||
request = require('request'),
|
|
||||||
crypto = require('crypto');
|
|
||||||
|
|
||||||
router.post('/sendMessage', function (req, res) {
|
|
||||||
let db = req.app.get('db');
|
|
||||||
let domain = req.app.get('domain');
|
|
||||||
let acct = req.body.acct;
|
|
||||||
let apikey = req.body.apikey;
|
|
||||||
let message = req.body.message;
|
|
||||||
// check to see if your API key matches
|
|
||||||
let result = db.prepare('select apikey from accounts where name = ?').get(`${acct}@${domain}`);
|
|
||||||
if (result.apikey === apikey) {
|
|
||||||
sendCreateMessage(message, acct, domain, req, res);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
res.status(403).json({msg: 'wrong api key'});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function signAndSend(message, name, domain, req, res, targetDomain, inbox) {
|
|
||||||
// get the private key
|
|
||||||
let db = req.app.get('db');
|
|
||||||
let inboxFragment = inbox.replace('https://'+targetDomain,'');
|
|
||||||
let result = db.prepare('select privkey from accounts where name = ?').get(`${name}@${domain}`);
|
|
||||||
if (result === undefined) {
|
|
||||||
console.log(`No record found for ${name}.`);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let privkey = result.privkey;
|
|
||||||
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="https://${domain}/u/${name}",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){
|
|
||||||
console.log(`Sent message to an inbox at ${targetDomain}!`);
|
|
||||||
if (error) {
|
|
||||||
console.log('Error:', error, response);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log('Response Status Code:', response.statusCode);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMessage(text, name, domain, req, res, follower) {
|
|
||||||
const guidCreate = crypto.randomBytes(16).toString('hex');
|
|
||||||
const guidNote = crypto.randomBytes(16).toString('hex');
|
|
||||||
let db = req.app.get('db');
|
|
||||||
let d = new Date();
|
|
||||||
|
|
||||||
let noteMessage = {
|
|
||||||
'id': `https://${domain}/m/${guidNote}`,
|
|
||||||
'type': 'Note',
|
|
||||||
'published': d.toISOString(),
|
|
||||||
'attributedTo': `https://${domain}/u/${name}`,
|
|
||||||
'content': text,
|
|
||||||
'to': ['https://www.w3.org/ns/activitystreams#Public'],
|
|
||||||
};
|
|
||||||
|
|
||||||
let createMessage = {
|
|
||||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
||||||
|
|
||||||
'id': `https://${domain}/m/${guidCreate}`,
|
|
||||||
'type': 'Create',
|
|
||||||
'actor': `https://${domain}/u/${name}`,
|
|
||||||
'to': ['https://www.w3.org/ns/activitystreams#Public'],
|
|
||||||
'cc': [follower],
|
|
||||||
|
|
||||||
'object': noteMessage
|
|
||||||
};
|
|
||||||
|
|
||||||
db.prepare('insert or replace into messages(guid, message) values(?, ?)').run( guidCreate, JSON.stringify(createMessage));
|
|
||||||
db.prepare('insert or replace into messages(guid, message) values(?, ?)').run( guidNote, JSON.stringify(noteMessage));
|
|
||||||
|
|
||||||
return createMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendCreateMessage(text, name, domain, req, res) {
|
|
||||||
let db = req.app.get('db');
|
|
||||||
|
|
||||||
let result = db.prepare('select followers from accounts where name = ?').get(`${name}@${domain}`);
|
|
||||||
let followers = JSON.parse(result.followers);
|
|
||||||
console.log(followers);
|
|
||||||
console.log('type',typeof followers);
|
|
||||||
if (followers === null) {
|
|
||||||
console.log('aaaa');
|
|
||||||
res.status(400).json({msg: `No followers for account ${name}@${domain}`});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (let follower of followers) {
|
|
||||||
let inbox = follower+'/inbox';
|
|
||||||
let myURL = new URL(follower);
|
|
||||||
let targetDomain = myURL.hostname;
|
|
||||||
let message = createMessage(text, name, domain, req, res, follower);
|
|
||||||
signAndSend(message, name, domain, req, res, targetDomain, inbox);
|
|
||||||
}
|
|
||||||
res.status(200).json({msg: 'ok'});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = router;
|
|
|
@ -1,14 +1,16 @@
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const utils = require('../utils')
|
const utils = require('../utils')
|
||||||
|
const pub = require('../pub')
|
||||||
|
const net = require('../net')
|
||||||
const request = require('request-promise-native')
|
const request = require('request-promise-native')
|
||||||
const httpSignature = require('http-signature')
|
const httpSignature = require('http-signature')
|
||||||
const {ObjectId} = require('mongodb')
|
const {ObjectId} = require('mongodb')
|
||||||
|
|
||||||
router.post('/', utils.validators.activity, function (req, res) {
|
router.post('/', net.validators.activity, function (req, res) {
|
||||||
const db = req.app.get('db');
|
const db = req.app.get('db');
|
||||||
let outgoingResponse
|
let outgoingResponse
|
||||||
req.body._meta = {_target: 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':
|
||||||
|
@ -31,7 +33,7 @@ router.post('/', utils.validators.activity, function (req, res) {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
utils.getOrCreateActor(req.user, db, true),
|
utils.getOrCreateActor(req.user, db, true),
|
||||||
request({
|
request({
|
||||||
url: utils.actorFromActivity(req.body),
|
url: pub.utils.actorFromActivity(req.body),
|
||||||
headers: {Accept: 'application/activity+json'},
|
headers: {Accept: 'application/activity+json'},
|
||||||
json: true,
|
json: true,
|
||||||
})
|
})
|
||||||
|
@ -53,7 +55,7 @@ router.post('/', utils.validators.activity, function (req, res) {
|
||||||
headers: ['(request-target)', 'host', 'date'],
|
headers: ['(request-target)', 'host', 'date'],
|
||||||
},
|
},
|
||||||
json: true,
|
json: true,
|
||||||
body: utils.toJSONLD({
|
body: pub.utils.toJSONLD({
|
||||||
_id: newID,
|
_id: newID,
|
||||||
type: 'Accept',
|
type: 'Accept',
|
||||||
id: `https://${req.app.get('domain')}/o/${newID.toHexString()}`,
|
id: `https://${req.app.get('domain')}/o/${newID.toHexString()}`,
|
||||||
|
@ -80,11 +82,11 @@ router.post('/', utils.validators.activity, function (req, res) {
|
||||||
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': 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(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()
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
api: require('./api'),
|
|
||||||
admin: require('./admin'),
|
|
||||||
user: require('./user'),
|
user: require('./user'),
|
||||||
message: require('./message'),
|
message: require('./message'),
|
||||||
inbox: require('./inbox'),
|
inbox: require('./inbox'),
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
const utils = require('../utils')
|
const utils = require('../utils')
|
||||||
|
const net = require('../net')
|
||||||
|
const pub = require('../pub')
|
||||||
|
|
||||||
router.post('/', utils.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),
|
||||||
|
@ -17,11 +19,11 @@ router.post('/', utils.validators.outboxActivity, function (req, res) {
|
||||||
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: 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(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()
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
const express = require('express'),
|
const express = require('express'),
|
||||||
router = express.Router();
|
router = express.Router();
|
||||||
const utils = require('../utils')
|
const utils = require('../utils')
|
||||||
const {toJSONLD} = require('../utils/index.js');
|
const pub = require('../pub')
|
||||||
|
|
||||||
router.get('/:name', async function (req, res) {
|
router.get('/:name', async function (req, res) {
|
||||||
let name = req.params.name;
|
let name = req.params.name;
|
||||||
|
@ -13,7 +13,7 @@ router.get('/:name', async function (req, res) {
|
||||||
let db = req.app.get('db')
|
let db = req.app.get('db')
|
||||||
const user = await utils.getOrCreateActor(name, db)
|
const user = await utils.getOrCreateActor(name, db)
|
||||||
if (user) {
|
if (user) {
|
||||||
return res.json(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')
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,13 @@ router.get('/:name/followers', function (req, res) {
|
||||||
db.collection('streams')
|
db.collection('streams')
|
||||||
.find({
|
.find({
|
||||||
type: 'Follow',
|
type: 'Follow',
|
||||||
'_meta._target': 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(utils.actorFromActivity)
|
const followers = follows.map(pub.utils.actorFromActivity)
|
||||||
return res.json(utils.arrayToCollection(followers))
|
return res.json(pub.utils.arrayToCollection(followers))
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ASContext: 'https://www.w3.org/ns/activitystreams',
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
const {promisify} = require('util')
|
const {promisify} = require('util')
|
||||||
const {ASContext} = require('./consts')
|
const utils = require('../utils')
|
||||||
const config = require('../config.json')
|
const config = require('../config.json')
|
||||||
|
const db = require('../db')
|
||||||
|
const pub = require('../pub')
|
||||||
|
|
||||||
module.exports.validators = require('./validators');
|
module.exports.consts = require('./consts')
|
||||||
|
|
||||||
function isObject(value) {
|
function isObject(value) {
|
||||||
return value && typeof value === 'object' && value.constructor === Object
|
return value && typeof value === 'object' && value.constructor === Object
|
||||||
|
@ -20,68 +22,11 @@ function traverseObject(obj, f) {
|
||||||
Object.keys(obj).forEach(traverse)
|
Object.keys(obj).forEach(traverse)
|
||||||
return f(obj);
|
return f(obj);
|
||||||
}
|
}
|
||||||
module.exports.toJSONLD = function (obj) {
|
|
||||||
obj['@context'] = obj['@context'] || ASContext;
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.arrayToCollection = function (arr, ordered) {
|
|
||||||
|
|
||||||
return {
|
|
||||||
'@context': ASContext,
|
|
||||||
totalItems: arr.length,
|
|
||||||
type: ordered ? 'orderedCollection' : 'collection',
|
|
||||||
[ordered ? 'orderedItems' : 'items']: arr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function usernameToIRI (user) {
|
|
||||||
return `https://${config.DOMAIN}/u/${user}`
|
|
||||||
}
|
|
||||||
module.exports.usernameToIRI = usernameToIRI
|
|
||||||
|
|
||||||
const generateKeyPairPromise = promisify(crypto.generateKeyPair)
|
|
||||||
function createLocalActor (name, type) {
|
|
||||||
return generateKeyPairPromise('rsa', {
|
|
||||||
modulusLength: 4096,
|
|
||||||
publicKeyEncoding: {
|
|
||||||
type: 'spki',
|
|
||||||
format: 'pem'
|
|
||||||
},
|
|
||||||
privateKeyEncoding: {
|
|
||||||
type: 'pkcs8',
|
|
||||||
format: 'pem',
|
|
||||||
}
|
|
||||||
}).then(pair => {
|
|
||||||
const actorBase = usernameToIRI(name);
|
|
||||||
return {
|
|
||||||
_meta: {
|
|
||||||
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`,
|
|
||||||
publicKey: {
|
|
||||||
'id': `${actorBase}#main-key`,
|
|
||||||
'owner': `${actorBase}`,
|
|
||||||
'publicKeyPem': pair.publicKey
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
module.exports.createLocalActor = createLocalActor
|
|
||||||
const actorProj = {_id: 0, _meta: 0}
|
const actorProj = {_id: 0, _meta: 0}
|
||||||
const metaActorProj = {_id: 0}
|
const metaActorProj = {_id: 0}
|
||||||
async function getOrCreateActor(preferredUsername, db, includeMeta) {
|
async function getOrCreateActor(preferredUsername, db, includeMeta) {
|
||||||
const id = usernameToIRI(preferredUsername)
|
const id = pub.utils.usernameToIRI(preferredUsername)
|
||||||
let user = await db.collection('objects')
|
let user = await db.collection('objects')
|
||||||
.find({id: id})
|
.find({id: id})
|
||||||
.limit(1)
|
.limit(1)
|
||||||
|
@ -92,7 +37,7 @@ async function getOrCreateActor(preferredUsername, db, includeMeta) {
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
// auto create groups whenever an unknown actor is referenced
|
// auto create groups whenever an unknown actor is referenced
|
||||||
user = await createLocalActor(preferredUsername, 'Group')
|
user = await pub.actor.createLocalActor(preferredUsername, 'Group')
|
||||||
await db.collection('objects').insertOne(user)
|
await db.collection('objects').insertOne(user)
|
||||||
// only executed on success
|
// only executed on success
|
||||||
delete user._id
|
delete user._id
|
||||||
|
@ -101,15 +46,4 @@ async function getOrCreateActor(preferredUsername, db, includeMeta) {
|
||||||
}
|
}
|
||||||
return user
|
return user
|
||||||
}
|
}
|
||||||
module.exports.getOrCreateActor = getOrCreateActor
|
module.exports.getOrCreateActor = getOrCreateActor
|
||||||
|
|
||||||
function actorFromActivity (activity) {
|
|
||||||
if (Object.prototype.toString.call(activity.actor) === '[object String]') {
|
|
||||||
return activity.actor
|
|
||||||
}
|
|
||||||
if (activity.actor.type === 'Link') {
|
|
||||||
return activity.actor.href
|
|
||||||
}
|
|
||||||
return activity.actor.id
|
|
||||||
}
|
|
||||||
module.exports.actorFromActivity = actorFromActivity
|
|
Loading…
Reference in a new issue