Cleanup, DRY, logging

This commit is contained in:
Bryan Ashby 2023-01-20 22:15:59 -07:00
parent 9517b292a4
commit d9e4b66a35
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
5 changed files with 145 additions and 113 deletions

View File

@ -18,9 +18,9 @@ const ActivityPubSettings = require('./settings');
// deps
const _ = require('lodash');
const https = require('https');
const isString = require('lodash/isString');
const mimeTypes = require('mime-types');
const { getJson } = require('../http_util.js');
// https://www.w3.org/TR/activitypub/#actor-objects
module.exports = class Actor {
@ -141,40 +141,18 @@ module.exports = class Actor {
};
// :TODO: use getJson()
https.get(url, { headers }, res => {
if (res.statusCode !== 200) {
return cb(Errors.Invalid(`Bad HTTP status code: ${res.statusCode}`));
getJson(url, { headers }, (err, actor) => {
if (err) {
return cb(err);
}
const contentType = res.headers['content-type'];
if (
!_.isString(contentType) ||
!contentType.startsWith('application/activity+json')
) {
return cb(Errors.Invalid(`Invalid Content-Type: ${contentType}`));
actor = new Actor(actor);
if (!actor.isValid()) {
return cb(Errors.Invalid('Invalid Actor'));
}
res.setEncoding('utf8');
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);
});
return cb(null, actor);
});
}

View File

@ -2,13 +2,42 @@ const { makeUserUrl } = require('./util');
const ActivityPubObject = require('./object');
const apDb = require('../database').dbs.activitypub;
const { getISOTimestampString } = require('../database');
const { isString } = require('lodash');
const { isString, get } = require('lodash');
module.exports = class Collection extends ActivityPubObject {
constructor(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) {
// :TODD: |includePrivate| handling
const followersUrl =
@ -24,12 +53,22 @@ module.exports = class Collection extends ActivityPubObject {
return cb(err);
}
const obj = {
id: followersUrl,
type: 'OrderedCollection',
first: `${followersUrl}?page=1`,
totalItems: row.count,
};
let obj;
if (row.count > 0) {
obj = {
id: followersUrl,
type: 'OrderedCollection',
first: `${followersUrl}?page=1`,
totalItems: row.count,
};
} else {
obj = {
id: followersUrl,
type: 'OrderedCollection',
totalItems: 0,
orderedItems: [],
};
}
return cb(null, new Collection(obj));
}
@ -48,7 +87,8 @@ module.exports = class Collection extends ActivityPubObject {
return cb(err);
}
if (mapper) {
entries = entries || [];
if (mapper && entries.length > 0) {
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) {
if (!isString(entry)) {
entry = JSON.stringify(entry);
}
apDb.run(
`INSERT INTO collection_entry (name, timestamp, user_id, entry_json)
VALUES (?, ?, ?, ?);`,
`INSERT OR IGNORE INTO collection_entry (name, timestamp, user_id, entry_json)
VALUES (?, ?, ?, ?);`,
[name, getISOTimestampString(), owningUser.userId, entry],
function res(err) {
// non-arrow for 'this' scope
if (err) {
return cb(err);
}
return cb(err, this.lastID);
}
);
}
static addFollower(owningUser, followingActor, cb) {
return Collection.addToCollection('followers', owningUser, followingActor, cb);
}
};

View File

@ -542,8 +542,13 @@ dbs.message.run(
);
dbs.activitypub.run(
`CREATE INDEX IF NOT EXISTS collection_entry_unique_index0
ON collection_entry (name, user_id, json_extract(entry_json, '$.id'))`
`CREATE INDEX IF NOT EXISTS collection_entry_by_user_index0
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);

View File

@ -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(
resp,
500,

View File

@ -64,7 +64,25 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
this.webServer.addRoute({
method: 'GET',
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
@ -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/
_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
const signature = this._parseAndValidateSignature(req);
@ -208,8 +271,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
Activity.fromOutboxEntries(user, this.webServer, (err, activity) => {
if (err) {
// :TODO: LOG ME
return this.webServer.internalServerError(resp);
return this.webServer.internalServerError(resp, err);
}
const body = JSON.stringify(activity);
@ -234,49 +296,8 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
}
_followersGetHandler(req, resp) {
this.log.trace({ url: req.url }, 'Request for "followers"');
// :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);
});
});
this.log.debug({ url: req.url }, 'Request for "followers"');
return this._getCollectionHandler('followers', req, resp);
}
_parseAndValidateSignature(req) {
@ -331,7 +352,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
}
_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
const accountName = accountFromSelfUrl(activity.object);
@ -346,8 +367,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
Actor.fromRemoteUrl(activity.actor, (err, actor) => {
if (err) {
// :TODO: log, and probably should be inspecting |err|
return this.webServer.internalServerError(resp);
return this.webServer.internalServerError(resp, err);
}
const pubKey = actor.publicKey;
@ -427,7 +447,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
return callback(null); // just a warning
}
this.log.trace(
this.log.info(
{ inbox: remoteActor.inbox },
'Remote server received our "Accept" successfully'
);
@ -463,8 +483,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
Actor.fromLocalUser(user, this.webServer, (err, actor) => {
if (err) {
// :TODO: Log me
return this.webServer.internalServerError(resp);
return this.webServer.internalServerError(resp, err);
}
const body = JSON.stringify(actor);