Skip to content

Instantly share code, notes, and snippets.

@audiodude
Last active November 21, 2023 02:13
Show Gist options
  • Save audiodude/c6ad0923dbe1c9c1199e6d021b6f1ed5 to your computer and use it in GitHub Desktop.
Save audiodude/c6ad0923dbe1c9c1199e6d021b6f1ed5 to your computer and use it in GitHub Desktop.
Manually POST to Mastodon inbox with HTTP signature in Node/JS
import * as fs from 'node:fs/promises';
import crypto from 'node:crypto';
import fetch from 'node-fetch';
const document = {
"actor": "https://travisbriggs.com/digital.garden",
"type": "Create",
"published": "2023-11-14T00:00:00.000Z",
"id": "https://travisbriggs.com/garden/compost/publish",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"object": {
"attributedTo": "https://travisbriggs.com/digital.garden",
"content": "<p>Compost pile</p><p><a href=\"https://travisbriggs.com/garden/compost/\">https://travisbriggs.com/garden/compost/</a></p>",
"id": "https://travisbriggs.com/garden/compost2",
"inReplyTo": null,
"published": "2023-11-14T00:00:00.000Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"inReplyTo": "https://sfba.social/@audiodude/111441191275986070",
"type": "Note",
"url": "https://travisbriggs.com/garden/compost2"
},
"@context": "https://www.w3.org/ns/activitystreams"
}
async function postToInbox(document) {
// Private key file that corresponds to public key embedded in the actor.json pointed
// to by the webfinger file.
const pem = await fs.readFile('private.pem');
const key = pem.toString('ascii');
const signer = crypto.createSign('RSA-SHA256');
const sha256 = crypto.createHash('sha256');
const digest = sha256.digest('base64');
const digestHeader = `sha-256=${digest}`;
const date = new Date().toUTCString();
const toSign = `(request-target): post /inbox\nhost: sfba.social\ndate: ${date}\ndigest: ${digestHeader}`;
signer.update(toSign);
const signature = signer.sign(key, 'base64');
const signatureHeader = `keyId="https://travisbriggs.com/digital.garden#main-key",headers="(request-target) host date digest",signature="${signature}"`;
return fetch('https://sfba.social/inbox', {
method: 'POST',
body: document,
headers: {
Host: 'sfba.social',
Date: date,
Digest: digestHeader,
Signature: signatureHeader,
},
});
}
postToInbox(document)
.then((resp) => {
console.log(resp.status);
return resp.text();
})
.then((text) => {
console.log(text);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment