Cleanup, DRY, logging
This commit is contained in:
parent
9517b292a4
commit
d9e4b66a35
|
@ -18,9 +18,9 @@ const ActivityPubSettings = require('./settings');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const https = require('https');
|
|
||||||
const isString = require('lodash/isString');
|
const isString = require('lodash/isString');
|
||||||
const mimeTypes = require('mime-types');
|
const mimeTypes = require('mime-types');
|
||||||
|
const { getJson } = require('../http_util.js');
|
||||||
|
|
||||||
// https://www.w3.org/TR/activitypub/#actor-objects
|
// https://www.w3.org/TR/activitypub/#actor-objects
|
||||||
module.exports = class Actor {
|
module.exports = class Actor {
|
||||||
|
@ -141,40 +141,18 @@ module.exports = class Actor {
|
||||||
};
|
};
|
||||||
|
|
||||||
// :TODO: use getJson()
|
// :TODO: use getJson()
|
||||||
|
getJson(url, { headers }, (err, actor) => {
|
||||||
https.get(url, { headers }, res => {
|
if (err) {
|
||||||
if (res.statusCode !== 200) {
|
return cb(err);
|
||||||
return cb(Errors.Invalid(`Bad HTTP status code: ${res.statusCode}`));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentType = res.headers['content-type'];
|
actor = new Actor(actor);
|
||||||
if (
|
|
||||||
!_.isString(contentType) ||
|
if (!actor.isValid()) {
|
||||||
!contentType.startsWith('application/activity+json')
|
return cb(Errors.Invalid('Invalid Actor'));
|
||||||
) {
|
|
||||||
return cb(Errors.Invalid(`Invalid Content-Type: ${contentType}`));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setEncoding('utf8');
|
return cb(null, actor);
|
||||||
let body = '';
|
|
||||||
res.on('data', data => {
|
|
||||||
body += data;
|
|
||||||
});
|
|
||||||
|
|
||||||
res.on('end', () => {
|
|
||||||
let actor;
|
|
||||||
try {
|
|
||||||
actor = Actor.fromJsonString(body);
|
|
||||||
} catch (e) {
|
|
||||||
return cb(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!actor.isValid()) {
|
|
||||||
return cb(Errors.Invalid('Invalid Actor'));
|
|
||||||
}
|
|
||||||
|
|
||||||
return cb(null, actor);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,42 @@ const { makeUserUrl } = require('./util');
|
||||||
const ActivityPubObject = require('./object');
|
const ActivityPubObject = require('./object');
|
||||||
const apDb = require('../database').dbs.activitypub;
|
const apDb = require('../database').dbs.activitypub;
|
||||||
const { getISOTimestampString } = require('../database');
|
const { getISOTimestampString } = require('../database');
|
||||||
const { isString } = require('lodash');
|
|
||||||
|
const { isString, get } = require('lodash');
|
||||||
|
|
||||||
module.exports = class Collection extends ActivityPubObject {
|
module.exports = class Collection extends ActivityPubObject {
|
||||||
constructor(obj) {
|
constructor(obj) {
|
||||||
super(obj);
|
super(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static followers(owningUser, page, webServer, cb) {
|
||||||
|
return Collection.getOrdered(
|
||||||
|
'followers',
|
||||||
|
owningUser,
|
||||||
|
false,
|
||||||
|
page,
|
||||||
|
e => e.id,
|
||||||
|
webServer,
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static following(owningUser, page, webServer, cb) {
|
||||||
|
return Collection.getOrdered(
|
||||||
|
'following',
|
||||||
|
owningUser,
|
||||||
|
false,
|
||||||
|
page,
|
||||||
|
e => get(e, 'object.id'),
|
||||||
|
webServer,
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static addFollower(owningUser, followingActor, cb) {
|
||||||
|
return Collection.addToCollection('followers', owningUser, followingActor, cb);
|
||||||
|
}
|
||||||
|
|
||||||
static getOrdered(name, owningUser, includePrivate, page, mapper, webServer, cb) {
|
static getOrdered(name, owningUser, includePrivate, page, mapper, webServer, cb) {
|
||||||
// :TODD: |includePrivate| handling
|
// :TODD: |includePrivate| handling
|
||||||
const followersUrl =
|
const followersUrl =
|
||||||
|
@ -24,12 +53,22 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const obj = {
|
let obj;
|
||||||
id: followersUrl,
|
if (row.count > 0) {
|
||||||
type: 'OrderedCollection',
|
obj = {
|
||||||
first: `${followersUrl}?page=1`,
|
id: followersUrl,
|
||||||
totalItems: row.count,
|
type: 'OrderedCollection',
|
||||||
};
|
first: `${followersUrl}?page=1`,
|
||||||
|
totalItems: row.count,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
obj = {
|
||||||
|
id: followersUrl,
|
||||||
|
type: 'OrderedCollection',
|
||||||
|
totalItems: 0,
|
||||||
|
orderedItems: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return cb(null, new Collection(obj));
|
return cb(null, new Collection(obj));
|
||||||
}
|
}
|
||||||
|
@ -48,7 +87,8 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mapper) {
|
entries = entries || [];
|
||||||
|
if (mapper && entries.length > 0) {
|
||||||
entries = entries.map(mapper);
|
entries = entries.map(mapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,35 +105,22 @@ module.exports = class Collection extends ActivityPubObject {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static followers(owningUser, page, webServer, cb) {
|
|
||||||
return Collection.getOrdered(
|
|
||||||
'followers',
|
|
||||||
owningUser,
|
|
||||||
false,
|
|
||||||
page,
|
|
||||||
e => e.id,
|
|
||||||
webServer,
|
|
||||||
cb
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static addToCollection(name, owningUser, entry, cb) {
|
static addToCollection(name, owningUser, entry, cb) {
|
||||||
if (!isString(entry)) {
|
if (!isString(entry)) {
|
||||||
entry = JSON.stringify(entry);
|
entry = JSON.stringify(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
apDb.run(
|
apDb.run(
|
||||||
`INSERT INTO collection_entry (name, timestamp, user_id, entry_json)
|
`INSERT OR IGNORE INTO collection_entry (name, timestamp, user_id, entry_json)
|
||||||
VALUES (?, ?, ?, ?);`,
|
VALUES (?, ?, ?, ?);`,
|
||||||
[name, getISOTimestampString(), owningUser.userId, entry],
|
[name, getISOTimestampString(), owningUser.userId, entry],
|
||||||
function res(err) {
|
function res(err) {
|
||||||
// non-arrow for 'this' scope
|
// non-arrow for 'this' scope
|
||||||
|
if (err) {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
return cb(err, this.lastID);
|
return cb(err, this.lastID);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static addFollower(owningUser, followingActor, cb) {
|
|
||||||
return Collection.addToCollection('followers', owningUser, followingActor, cb);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -542,8 +542,13 @@ dbs.message.run(
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.activitypub.run(
|
dbs.activitypub.run(
|
||||||
`CREATE INDEX IF NOT EXISTS collection_entry_unique_index0
|
`CREATE INDEX IF NOT EXISTS collection_entry_by_user_index0
|
||||||
ON collection_entry (name, user_id, json_extract(entry_json, '$.id'))`
|
ON collection_entry (name, user_id);`
|
||||||
|
);
|
||||||
|
|
||||||
|
dbs.activitypub.run(
|
||||||
|
`CREATE UNIQUE INDEX IF NOT EXISTS collection_entry_unique_index0
|
||||||
|
ON collection_entry (name, user_id, json_extract(entry_json, '$.id'));`
|
||||||
);
|
);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
|
|
|
@ -334,7 +334,10 @@ exports.getModule = class WebServerModule extends ServerModule {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
internalServerError(resp) {
|
internalServerError(resp, err) {
|
||||||
|
if (err) {
|
||||||
|
this.log.error({ error: err.message }, 'Internal server error');
|
||||||
|
}
|
||||||
return this.respondWithError(
|
return this.respondWithError(
|
||||||
resp,
|
resp,
|
||||||
500,
|
500,
|
||||||
|
|
|
@ -64,7 +64,25 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
this.webServer.addRoute({
|
this.webServer.addRoute({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
path: /^\/_enig\/ap\/users\/.+\/followers(\?page=[0-9]+)?$/,
|
path: /^\/_enig\/ap\/users\/.+\/followers(\?page=[0-9]+)?$/,
|
||||||
handler: this._followersGetHandler.bind(this),
|
handler: (req, resp) => {
|
||||||
|
return this._enforceSigningPolicy(
|
||||||
|
req,
|
||||||
|
resp,
|
||||||
|
this._followersGetHandler.bind(this)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.webServer.addRoute({
|
||||||
|
method: 'GET',
|
||||||
|
path: /^\/_enig\/ap\/users\/.+\/following(\?page=[0-9]+)?$/,
|
||||||
|
handler: (req, resp) => {
|
||||||
|
return this._enforceSigningPolicy(
|
||||||
|
req,
|
||||||
|
resp,
|
||||||
|
this._followingGetHandler.bind(this)
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// :TODO: NYI
|
// :TODO: NYI
|
||||||
|
@ -180,9 +198,54 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_getCollectionHandler(name, req, resp) {
|
||||||
|
const url = new URL(req.url, `https://${req.headers.host}`);
|
||||||
|
const accountName = this._accountNameFromUserPath(url, name);
|
||||||
|
if (!accountName) {
|
||||||
|
return this.webServer.resourceNotFound(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// can we even handle this request?
|
||||||
|
const getter = Collection[name];
|
||||||
|
if (!getter) {
|
||||||
|
return this.webServer.resourceNotFound(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
userFromAccount(accountName, (err, user) => {
|
||||||
|
if (err) {
|
||||||
|
this.log.info(
|
||||||
|
{ reason: err.message, accountName: accountName },
|
||||||
|
`No user "${accountName}" for "${name}"`
|
||||||
|
);
|
||||||
|
return this.webServer.resourceNotFound(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = url.searchParams.get('page');
|
||||||
|
getter(user, page, this.webServer, (err, collection) => {
|
||||||
|
if (err) {
|
||||||
|
return this.webServer.internalServerError(resp, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = JSON.stringify(collection);
|
||||||
|
const headers = {
|
||||||
|
'Content-Type': ActivityJsonMime,
|
||||||
|
'Content-Length': body.length,
|
||||||
|
};
|
||||||
|
|
||||||
|
resp.writeHead(200, headers);
|
||||||
|
return resp.end(body);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_followingGetHandler(req, resp) {
|
||||||
|
this.log.debug({ url: req.url }, 'Request for "following"');
|
||||||
|
return this._getCollectionHandler('following', req, resp);
|
||||||
|
}
|
||||||
|
|
||||||
// https://docs.gotosocial.org/en/latest/federation/behaviors/outbox/
|
// https://docs.gotosocial.org/en/latest/federation/behaviors/outbox/
|
||||||
_outboxGetHandler(req, resp) {
|
_outboxGetHandler(req, resp) {
|
||||||
this.log.trace({ url: req.url }, 'Request for "outbox"');
|
this.log.debug({ url: req.url }, 'Request for "outbox"');
|
||||||
|
|
||||||
// the request must be signed, and the signature must be valid
|
// the request must be signed, and the signature must be valid
|
||||||
const signature = this._parseAndValidateSignature(req);
|
const signature = this._parseAndValidateSignature(req);
|
||||||
|
@ -208,8 +271,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
Activity.fromOutboxEntries(user, this.webServer, (err, activity) => {
|
Activity.fromOutboxEntries(user, this.webServer, (err, activity) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
// :TODO: LOG ME
|
return this.webServer.internalServerError(resp, err);
|
||||||
return this.webServer.internalServerError(resp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = JSON.stringify(activity);
|
const body = JSON.stringify(activity);
|
||||||
|
@ -234,49 +296,8 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
_followersGetHandler(req, resp) {
|
_followersGetHandler(req, resp) {
|
||||||
this.log.trace({ url: req.url }, 'Request for "followers"');
|
this.log.debug({ url: req.url }, 'Request for "followers"');
|
||||||
|
return this._getCollectionHandler('followers', req, resp);
|
||||||
// :TODO: dry this stuff..
|
|
||||||
|
|
||||||
// the request must be signed, and the signature must be valid
|
|
||||||
const signature = this._parseAndValidateSignature(req);
|
|
||||||
if (!signature) {
|
|
||||||
return this.webServer.accessDenied(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// /_enig/ap/users/SomeName/outbox -> SomeName
|
|
||||||
const url = new URL(req.url, `https://${req.headers.host}`);
|
|
||||||
const accountName = this._accountNameFromUserPath(url, 'followers');
|
|
||||||
if (!accountName) {
|
|
||||||
return this.webServer.resourceNotFound(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
userFromAccount(accountName, (err, user) => {
|
|
||||||
if (err) {
|
|
||||||
this.log.info(
|
|
||||||
{ reason: err.message, accountName: accountName },
|
|
||||||
`No user "${accountName}" for "self"`
|
|
||||||
);
|
|
||||||
return this.webServer.resourceNotFound(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
const page = url.searchParams.get('page');
|
|
||||||
Collection.followers(user, page, this.webServer, (err, collection) => {
|
|
||||||
if (err) {
|
|
||||||
// :TODO: LOG ME
|
|
||||||
return this.webServer.internalServerError(resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.stringify(collection);
|
|
||||||
const headers = {
|
|
||||||
'Content-Type': ActivityJsonMime,
|
|
||||||
'Content-Length': body.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
resp.writeHead(200, headers);
|
|
||||||
return resp.end(body);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_parseAndValidateSignature(req) {
|
_parseAndValidateSignature(req) {
|
||||||
|
@ -331,7 +352,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
_withUserRequestHandler(signature, activity, activityHandler, req, resp) {
|
_withUserRequestHandler(signature, activity, activityHandler, req, resp) {
|
||||||
this.log.trace({ actor: activity.actor }, `Inbox request from ${activity.actor}`);
|
this.log.debug({ actor: activity.actor }, `Inbox request from ${activity.actor}`);
|
||||||
|
|
||||||
// :TODO: trace
|
// :TODO: trace
|
||||||
const accountName = accountFromSelfUrl(activity.object);
|
const accountName = accountFromSelfUrl(activity.object);
|
||||||
|
@ -346,8 +367,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
Actor.fromRemoteUrl(activity.actor, (err, actor) => {
|
Actor.fromRemoteUrl(activity.actor, (err, actor) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
// :TODO: log, and probably should be inspecting |err|
|
return this.webServer.internalServerError(resp, err);
|
||||||
return this.webServer.internalServerError(resp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pubKey = actor.publicKey;
|
const pubKey = actor.publicKey;
|
||||||
|
@ -427,7 +447,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
return callback(null); // just a warning
|
return callback(null); // just a warning
|
||||||
}
|
}
|
||||||
|
|
||||||
this.log.trace(
|
this.log.info(
|
||||||
{ inbox: remoteActor.inbox },
|
{ inbox: remoteActor.inbox },
|
||||||
'Remote server received our "Accept" successfully'
|
'Remote server received our "Accept" successfully'
|
||||||
);
|
);
|
||||||
|
@ -463,8 +483,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
|
||||||
|
|
||||||
Actor.fromLocalUser(user, this.webServer, (err, actor) => {
|
Actor.fromLocalUser(user, this.webServer, (err, actor) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
// :TODO: Log me
|
return this.webServer.internalServerError(resp, err);
|
||||||
return this.webServer.internalServerError(resp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = JSON.stringify(actor);
|
const body = JSON.stringify(actor);
|
||||||
|
|
Loading…
Reference in New Issue