Skip to content

Commit

Permalink
Merge pull request #127 from lowercasename/rk/spec-compliant-ap-headers
Browse files Browse the repository at this point in the history
Send and accept only spec-compliant ActivityPub headers
  • Loading branch information
lowercasename authored Feb 2, 2024
2 parents fbd2dd2 + 7bd4eb7 commit ecff04b
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 178 deletions.
4 changes: 2 additions & 2 deletions cypress/e2e/event.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ describe("Events", () => {
cy.request({
url: `/${this.eventID}/featured`,
headers: {
Accept: "application/activity+json",
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
},
}).then((response) => {
expect(response.body).to.have.property("@context");
Expand All @@ -139,7 +139,7 @@ describe("Events", () => {
this.eventID
}@${Cypress.env("CYPRESS_DOMAIN")}`,
headers: {
Accept: "application/activity+json",
Accept: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
},
}).then((response) => {
expect(response.body).to.have.property("subject");
Expand Down
99 changes: 50 additions & 49 deletions src/activitypub.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const domain = config.general.domain;
const siteName = config.general.site_name;
const isFederated = config.general.is_federated;
import Event from "./models/Event.js";
import { activityPubContentType, alternateActivityPubContentType } from "./lib/activitypub.js";

// This alphabet (used to generate all event, group, etc. IDs) is missing '-'
// because ActivityPub doesn't like it in IDs
Expand All @@ -35,9 +36,9 @@ export function createActivityPubActor(
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"toot": "http://joinmastodon.org/ns#",
"discoverable": "toot:discoverable",
"indexable": "toot:indexable"
"toot": "http://joinmastodon.org/ns#",
"discoverable": "toot:discoverable",
"indexable": "toot:indexable"
},
],
indexable: false,
Expand Down Expand Up @@ -93,9 +94,9 @@ export function createActivityPubEvent(
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"toot": "http://joinmastodon.org/ns#",
"discoverable": "toot:discoverable",
"indexable": "toot:indexable"
"toot": "http://joinmastodon.org/ns#",
"discoverable": "toot:discoverable",
"indexable": "toot:indexable"
},
],
indexable: false,
Expand Down Expand Up @@ -219,8 +220,8 @@ export function signAndSend(message, eventID, targetDomain, inbox, callback) {
Date: d.toUTCString(),
Signature: header,
Digest: `SHA-256=${digest}`,
"Content-Type": "application/activity+json",
Accept: "application/activity+json",
"Content-Type": activityPubContentType,
Accept: activityPubContentType,
},
method: "POST",
json: true,
Expand Down Expand Up @@ -257,7 +258,7 @@ export function signAndSend(message, eventID, targetDomain, inbox, callback) {
"addActivityPubMessage",
"success",
"ActivityPubMessage added to event " +
eventID,
eventID,
);
callback(null, message.id, 200);
})
Expand All @@ -266,9 +267,9 @@ export function signAndSend(message, eventID, targetDomain, inbox, callback) {
"addActivityPubMessage",
"error",
"Attempt to add ActivityPubMessage to event " +
eventID +
" failed with error: " +
err,
eventID +
" failed with error: " +
err,
);
callback(err, null, 500);
});
Expand Down Expand Up @@ -463,7 +464,7 @@ export function broadcastUpdateMessage(apObject, followers, eventID) {
}

export function broadcastDeleteMessage(apObject, followers, eventID, callback) {
callback = callback || function () {};
callback = callback || function () { };
if (!isFederated) {
callback([]);
return;
Expand Down Expand Up @@ -560,7 +561,7 @@ export function broadcastDeleteMessage(apObject, followers, eventID, callback) {
// this sends a message "to:" an individual fediverse user
export function sendDirectMessage(apObject, actorId, eventID, callback) {
if (!isFederated) return;
callback = callback || function () {};
callback = callback || function () { };
const guidCreate = crypto.randomBytes(16).toString("hex");
const guidObject = crypto.randomBytes(16).toString("hex");
let d = new Date();
Expand Down Expand Up @@ -618,7 +619,7 @@ export function sendDirectMessage(apObject, actorId, eventID, callback) {

export function sendAcceptMessage(thebody, eventID, targetDomain, callback) {
if (!isFederated) return;
callback = callback || function () {};
callback = callback || function () { };
const guid = crypto.randomBytes(16).toString("hex");
const actorId = thebody.actor;
let message = {
Expand Down Expand Up @@ -666,8 +667,8 @@ function _handleFollow(req, res) {
{
url: req.body.actor,
headers: {
Accept: "application/activity+json",
"Content-Type": "application/activity+json",
Accept: activityPubContentType,
"Content-Type": activityPubContentType,
},
},
function (error, response, body) {
Expand Down Expand Up @@ -794,9 +795,9 @@ function _handleFollow(req, res) {
"addEventFollower",
"error",
"Attempt to add follower to event " +
eventID +
" failed with error: " +
err,
eventID +
" failed with error: " +
err,
);
return res
.status(500)
Expand Down Expand Up @@ -851,9 +852,9 @@ function _handleUndoFollow(req, res) {
"removeEventFollower",
"error",
"Attempt to remove follower from event " +
eventID +
" failed with error: " +
err,
eventID +
" failed with error: " +
err,
);
return res.send(
"Database error, please try again :(",
Expand Down Expand Up @@ -887,8 +888,8 @@ function _handleAcceptEvent(req, res) {
{
url: actor,
headers: {
Accept: "application/activity+json",
"Content-Type": "application/activity+json",
Accept: activityPubContentType,
"Content-Type": activityPubContentType,
},
},
function (error, response, body) {
Expand All @@ -913,7 +914,7 @@ function _handleAcceptEvent(req, res) {
"addEventAttendee",
"success",
"Attendee added to event " +
req.params.eventID,
req.params.eventID,
);
// get the new attendee with its hidden id from the full event
let fullAttendee = fullEvent.attendees.find(
Expand Down Expand Up @@ -947,9 +948,9 @@ function _handleAcceptEvent(req, res) {
"addEventAttendee",
"error",
"Attempt to add attendee to event " +
req.params.eventID +
" failed with error: " +
err,
req.params.eventID +
" failed with error: " +
err,
);
return res
.status(500)
Expand Down Expand Up @@ -996,7 +997,7 @@ function _handleUndoAcceptEvent(req, res) {
"oneClickUnattend",
"success",
"Attendee removed via one click unattend " +
req.params.eventID,
req.params.eventID,
);
});
}
Expand Down Expand Up @@ -1039,8 +1040,8 @@ function _handleCreateNote(req, res) {
{
url: attributedTo,
headers: {
Accept: "application/activity+json",
"Content-Type": "application/activity+json",
Accept: activityPubContentType,
"Content-Type": activityPubContentType,
},
},
function (error, response, body) {
Expand Down Expand Up @@ -1069,7 +1070,7 @@ function _handleCreateNote(req, res) {
"addEventAttendee",
"success",
"Attendee added to event " +
req.params.eventID,
req.params.eventID,
);
// get the new attendee with its hidden id from the full event
let fullAttendee =
Expand Down Expand Up @@ -1105,9 +1106,9 @@ function _handleCreateNote(req, res) {
"addEventAttendee",
"error",
"Attempt to add attendee to event " +
req.params.eventID +
" failed with error: " +
err,
req.params.eventID +
" failed with error: " +
err,
);
return res
.status(500)
Expand Down Expand Up @@ -1169,7 +1170,7 @@ function _handleDelete(req, res) {
return (
comment.activityJson &&
JSON.parse(comment.activityJson).object.id ===
req.body.object.id
req.body.object.id
);
},
);
Expand All @@ -1189,11 +1190,11 @@ function _handleDelete(req, res) {
"deleteComment",
"error",
"Attempt to delete comment " +
req.body.object.id +
"from event " +
eventWithComment.id +
" failed with error: " +
err,
req.body.object.id +
"from event " +
eventWithComment.id +
" failed with error: " +
err,
);
return res.sendStatus(500);
});
Expand Down Expand Up @@ -1233,8 +1234,8 @@ function _handleCreateNoteComment(req, res) {
{
url: req.body.actor,
headers: {
Accept: "application/activity+json",
"Content-Type": "application/activity+json",
Accept: activityPubContentType,
"Content-Type": activityPubContentType,
},
},
function (error, response, actor) {
Expand Down Expand Up @@ -1296,13 +1297,13 @@ function _handleCreateNoteComment(req, res) {
"addEventComment",
"error",
"Attempt to add comment to event " +
eventID +
" failed with error: " +
err,
eventID +
" failed with error: " +
err,
);
res.status(500).send(
"Database error, please try again :(" +
err,
err,
);
});
},
Expand Down Expand Up @@ -1387,7 +1388,7 @@ export function createWebfinger(eventID, domain) {
links: [
{
rel: "self",
type: "application/activity+json",
type: alternateActivityPubContentType,
href: `https://${domain}/${eventID}`,
},
],
Expand Down
8 changes: 6 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import group from "./routes/group.js";
import staticPages from "./routes/static.js";

import { initEmailService } from "./lib/email.js";
import {
activityPubContentType,
alternateActivityPubContentType,
} from "./lib/activitypub.js";

const app = express();

Expand Down Expand Up @@ -48,8 +52,8 @@ app.set("hbsInstance", hbsInstance);
app.use(express.static("public"));

// Body parser //
app.use(express.json({ type: "application/activity+json" }));
app.use(express.json({ type: "application/ld+json" }));
app.use(express.json({ type: alternateActivityPubContentType }));
app.use(express.json({ type: activityPubContentType }));
app.use(express.json({ type: "application/json" }));
app.use(express.urlencoded({ extended: true }));

Expand Down
23 changes: 18 additions & 5 deletions src/lib/activitypub.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { Request } from "express";
import { Request, Response } from "express";

// From https://www.w3.org/TR/activitypub/#client-to-server-interactions:
// "Servers MAY interpret a Content-Type or Accept header of application/activity+json
// as equivalent to application/ld+json; profile="https://www.w3.org/ns/activitystreams"
// for client-to-server interactions.
// For best compatibility, we always send application/ld+json; profile="https://www.w3.org/ns/activitystreams"
// and accept both application/ld+json; profile="https://www.w3.org/ns/activitystreams" and application/activity+json.
export const activityPubContentType =
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"';
export const alternateActivityPubContentType = "application/activity+json";

// Cf. https://www.w3.org/TR/activitypub/#retrieving-objects
export const acceptsActivityPub = (req: Request) => {
return (
req.headers.accept &&
(req.headers.accept.includes("application/activity+json") ||
req.headers.accept.includes("application/ld+json"))
const validAcceptHeaders = [
activityPubContentType,
alternateActivityPubContentType,
];
return validAcceptHeaders.some(
(header) => req.headers.accept?.includes(header),
);
};
Loading

0 comments on commit ecff04b

Please sign in to comment.