Cleanup, DRY, logging
This commit is contained in:
parent
9517b292a4
commit
d9e4b66a35
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue