Added HTTP util: postJson(), post Accept Activity to server with signing

This commit is contained in:
Bryan Ashby 2023-01-08 20:34:30 -07:00
parent 3a70cc6939
commit 44c67f5327
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
4 changed files with 136 additions and 5 deletions

View File

@ -1,4 +1,3 @@
const { selfUrl } = require('./activitypub_util');
const UserProps = require('./user_property'); const UserProps = require('./user_property');
module.exports = class ActivityPubSettings { module.exports = class ActivityPubSettings {

View File

@ -42,7 +42,7 @@ exports.Errors = {
UnexpectedState: (reason, reasonCode) => UnexpectedState: (reason, reasonCode) =>
new EnigError('Unexpected state', -32007, reason, reasonCode), new EnigError('Unexpected state', -32007, reason, reasonCode),
MissingParam: (reason, reasonCode) => MissingParam: (reason, reasonCode) =>
new EnigError('Missing paramter(s)', -32008, reason, reasonCode), new EnigError('Missing parameter(s)', -32008, reason, reasonCode),
MissingMci: (reason, reasonCode) => MissingMci: (reason, reasonCode) =>
new EnigError('Missing required MCI code(s)', -32009, reason, reasonCode), new EnigError('Missing required MCI code(s)', -32009, reason, reasonCode),
BadLogin: (reason, reasonCode) => BadLogin: (reason, reasonCode) =>
@ -51,6 +51,9 @@ exports.Errors = {
new EnigError('User interrupted', -32011, reason, reasonCode), new EnigError('User interrupted', -32011, reason, reasonCode),
NothingToDo: (reason, reasonCode) => NothingToDo: (reason, reasonCode) =>
new EnigError('Nothing to do', -32012, reason, reasonCode), new EnigError('Nothing to do', -32012, reason, reasonCode),
HttpError: (reason, reasonCode) =>
new EnigError('HTTP error', -32013, reason, reasonCode),
Timeout: (reason, reasonCode) => new EnigError('Timeout', -32014, reason, reasonCode),
}; };
exports.ErrorReasons = { exports.ErrorReasons = {

72
core/http_util.js Normal file
View File

@ -0,0 +1,72 @@
const { Errors } = require('./enig_error.js');
// deps
const { isString, isObject } = require('lodash');
const https = require('https');
const httpSignature = require('http-signature');
const crypto = require('crypto');
exports.postJson = postJson;
function postJson(url, json, options, cb) {
if (!isString(json)) {
json = JSON.stringify(json);
}
const defaultOptions = {
method: 'POST',
body: json,
headers: {
'Content-Type': 'application/json',
},
};
options = Object.assign({}, defaultOptions, options);
if (options?.sign?.headers?.includes('digest')) {
options.headers['Digest'] = `SHA-256=${crypto
.createHash('sha256')
.update(json)
.digest('base64')}`;
}
options.headers['Content-Length'] = json.length;
const req = https.request(url, options, res => {
let body = [];
res.on('data', d => {
body.push(d);
});
res.on('end', () => {
body = Buffer.concat(body).toString();
if (res.statusCode < 200 || res.statusCode > 299) {
return cb(Errors.HttpError(`HTTP error ${res.statusCode}: ${body}`));
}
return cb(null, body, res);
});
});
if (isObject(options.sign)) {
try {
httpSignature.sign(req, options.sign);
} catch (e) {
req.destroy();
return cb(Errors.Invalid(`Invalid signing material: ${e}`));
}
}
req.on('error', err => {
return cb(err);
});
req.on('timeout', () => {
req.destroy();
return cb(Errors.Timeout('Timeout making HTTP request'));
});
req.write(json);
req.end();
}

View File

@ -4,18 +4,20 @@ const {
getUserProfileTemplatedBody, getUserProfileTemplatedBody,
DefaultProfileTemplate, DefaultProfileTemplate,
accountFromSelfUrl, accountFromSelfUrl,
selfUrl,
} = require('../../../activitypub_util'); } = require('../../../activitypub_util');
const Config = require('../../../config').get; const Config = require('../../../config').get;
const Activity = require('../../../activitypub_activity'); const Activity = require('../../../activitypub_activity');
const ActivityPubSettings = require('../../../activitypub_settings'); const ActivityPubSettings = require('../../../activitypub_settings');
const Actor = require('../../../activitypub_actor');
const { postJson } = require('../../../http_util');
const UserProps = require('../../../user_property');
// deps // deps
const _ = require('lodash'); const _ = require('lodash');
const enigma_assert = require('../../../enigma_assert'); const enigma_assert = require('../../../enigma_assert');
const httpSignature = require('http-signature'); const httpSignature = require('http-signature');
const https = require('https'); const https = require('https');
const { Errors } = require('../../../enig_error');
const Actor = require('../../../activitypub_actor');
exports.moduleInfo = { exports.moduleInfo = {
name: 'ActivityPub', name: 'ActivityPub',
@ -220,12 +222,67 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
return; return;
} }
// user must have a Private Key
const privateKey = user.getProperty(UserProps.PrivateKeyMain);
if (_.isEmpty(privateKey)) {
// :TODO: Log me
return;
}
const accept = Activity.makeAccept( const accept = Activity.makeAccept(
this.webServer, this.webServer,
localActor, localActor,
activity activity
); );
console.log(accept);
const keyId = selfUrl(this.webServer, user) + '#main-key';
const reqOpts = {
headers: {
'Content-Type': 'application/activity+json',
},
sign: {
// :TODO: Make a helper for this
key: privateKey,
keyId,
authorizationHeaderName: 'Signature',
headers: [
'(request-target)',
'host',
'date',
'digest',
'content-type',
],
},
};
postJson(
actor.inbox,
JSON.stringify(accept),
reqOpts,
(err, respBody, res) => {
if (err) {
return this.log.warn(
{
inbox: actor.inbox,
statusCode: res.statusCode,
error: err.message,
},
'Failed POSTing "Accept" to inbox'
);
}
if (res.statusCode != 202) {
return this.log.warn(
{
inbox: actor.inbox,
statusCode: res.statusCode,
},
'Unexpected status code'
);
}
}
);
}); });
} }