Added ability for Actor to sign requests

ref https://linear.app/tryghost/issue/MOM-74

This will allow us to generated signed requests for Activites.
This commit is contained in:
Fabien O'Carroll 2024-05-06 13:57:34 +07:00 committed by Fabien 'egg' O'Carroll
parent deb6e05889
commit e6552ddb63
2 changed files with 65 additions and 0 deletions

View File

@ -0,0 +1,57 @@
import crypto from 'node:crypto';
import {Actor} from './actor.entity';
import {HTTPSignature} from './http-signature.service';
import assert from 'node:assert';
describe('Actor', function () {
describe('#sign', function () {
it('returns a request with a valid Signature header', async function () {
const keypair = crypto.generateKeyPairSync('rsa', {
modulusLength: 512
});
const baseUrl = new URL('https://example.com/ap');
const actor = Actor.create({
username: 'Testing',
outbox: [],
publicKey: keypair.publicKey
.export({type: 'pkcs1', format: 'pem'})
.toString(),
privateKey: keypair.privateKey
.export({type: 'pkcs1', format: 'pem'})
.toString()
});
const url = new URL('https://some-server.com/users/username/inbox');
const date = new Date();
const request = new Request(url, {
headers: {
Host: url.host,
Date: date.toISOString(),
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
}
});
const signedRequest = await actor.sign(request, baseUrl);
const publicKey = actor.getJSONLD(baseUrl).publicKey;
class MockHTTPSignature extends HTTPSignature {
protected static async getPublicKey() {
return crypto.createPublicKey(publicKey.publicKeyPem);
}
}
const signedRequestURL = new URL(signedRequest.url);
const actual = await MockHTTPSignature.validate(
signedRequest.method,
signedRequestURL.pathname,
signedRequest.headers
);
const expected = true;
assert.equal(actual, expected, 'The signature should have been valid');
});
});
});

View File

@ -1,9 +1,11 @@
import crypto from 'crypto';
import ObjectID from 'bson-objectid';
import {Entity} from '../../common/entity.base';
import {ActivityPub} from './types';
import {Activity} from './activity.object';
import {Article} from './article.object';
import {ActivityEvent} from './activity.event';
import {HTTPSignature} from './http-signature.service';
type ActorData = {
username: string;
@ -25,6 +27,12 @@ export class Actor extends Entity<ActorData> {
return this.attr.outbox;
}
async sign(request: Request, baseUrl: URL): Promise<Request> {
const keyId = new URL(this.getJSONLD(baseUrl).publicKey.id);
const key = crypto.createPrivateKey(this.attr.privateKey);
return HTTPSignature.sign(request, keyId, key);
}
private activities: Activity[] = [];
static getActivitiesToSave(actor: Actor, fn: (activities: Activity[]) => void) {