Refactor configs and activitypub parts

This commit is contained in:
Jesse Wierzbinski 2023-10-15 20:04:03 -10:00
parent ca7d325cb1
commit c0ff46559b
17 changed files with 251 additions and 70 deletions

View file

@ -1,7 +1,7 @@
import { errorResponse, jsonResponse } from "@response";
import { MatchedRoute } from "bun";
import { User } from "~database/entities/User";
import { getHost } from "@config";
import { getConfig, getHost } from "@config";
/**
* ActivityPub WebFinger endpoint
@ -14,6 +14,8 @@ export default async (
const resource = matchedRoute.query.resource;
const requestedUser = resource.split("acct:")[1];
const config = getConfig();
// Check if user is a local user
if (requestedUser.split("@")[1] !== getHost()) {
return errorResponse("User is a remote user", 404);
@ -32,17 +34,17 @@ export default async (
{
rel: "self",
type: "application/activity+json",
href: `https://${getHost()}/@${user.username}/actor`
href: `${config.http.base_url}/users/${user.username}/actor`
},
{
rel: "https://webfinger.net/rel/profile-page",
type: "text/html",
href: `https://${getHost()}/@${user.username}`
href: `${config.http.base_url}/users/${user.username}`
},
{
rel: "self",
type: "application/activity+json; profile=\"https://www.w3.org/ns/activitystreams\"",
href: `https://${getHost()}/@${user.username}/actor`
href: `${config.http.base_url}/users/${user.username}/actor`
}
]
})

View file

@ -74,8 +74,6 @@ export default async (req: Request): Promise<Response> => {
}
);
config.validation.max_username_size;
// Check if username is valid
if (!body.username?.match(/^[a-zA-Z0-9_]+$/))
errors.details.username.push({
@ -83,6 +81,18 @@ export default async (req: Request): Promise<Response> => {
description: `must only contain letters, numbers, and underscores`,
});
// Check if username doesnt match filters
if (
config.filters.username_filters.some(
filter => body.username?.match(filter)
)
) {
errors.details.username.push({
error: "ERR_INVALID",
description: `contains blocked words`,
});
}
// Check if username is too long
if ((body.username?.length ?? 0) > config.validation.max_username_size)
errors.details.username.push({

View file

@ -62,6 +62,15 @@ export default async (req: Request): Promise<Response> => {
);
}
// Check if display name doesnt match filters
if (
config.filters.displayname_filters.some(filter =>
display_name.match(filter)
)
) {
return errorResponse("Display name contains blocked words", 422);
}
user.actor.data.name = display_name;
user.display_name = display_name;
}
@ -75,6 +84,11 @@ export default async (req: Request): Promise<Response> => {
);
}
// Check if bio doesnt match filters
if (config.filters.bio_filters.some(filter => note.match(filter))) {
return errorResponse("Bio contains blocked words", 422);
}
user.actor.data.summary = note;
user.note = note;
}

View file

@ -125,6 +125,11 @@ export default async (req: Request): Promise<Response> => {
);
}
// Check if status body doesnt match filters
if (config.filters.note_filters.some(filter => status.match(filter))) {
return errorResponse("Status contains blocked words", 422);
}
// Create status
const newStatus = await Status.createNew({
account: user,

View file

@ -89,7 +89,7 @@ export default async (
Hashtag: "as:Hashtag",
},
],
id: `${config.http.base_url}/@${user.username}`,
id: `${config.http.base_url}/users/${user.username}`,
type: "Person",
preferredUsername: user.username, // TODO: Add user display name
name: user.username,
@ -104,21 +104,21 @@ export default async (
url: user.header,
mediaType: "image/png", // TODO: Set user header mimetype
},
inbox: `${config.http.base_url}/@${user.username}/inbox`,
outbox: `${config.http.base_url}/@${user.username}/outbox`,
followers: `${config.http.base_url}/@${user.username}/followers`,
following: `${config.http.base_url}/@${user.username}/following`,
liked: `${config.http.base_url}/@${user.username}/liked`,
inbox: `${config.http.base_url}/users/${user.username}/inbox`,
outbox: `${config.http.base_url}/users/${user.username}/outbox`,
followers: `${config.http.base_url}/users/${user.username}/followers`,
following: `${config.http.base_url}/users/${user.username}/following`,
liked: `${config.http.base_url}/users/${user.username}/liked`,
discoverable: true,
alsoKnownAs: [
// TODO: Add accounts from which the user migrated
],
manuallyApprovesFollowers: false, // TODO: Change
publicKey: {
id: `${getHost()}${config.http.base_url}/@${
id: `${getHost()}${config.http.base_url}/users/${
user.username
}/actor#main-key`,
owner: `${config.http.base_url}/@${user.username}`,
owner: `${config.http.base_url}/users/${user.username}`,
// Split the public key into PEM format
publicKeyPem: `-----BEGIN PUBLIC KEY-----\n${user.public_key
.match(/.{1,64}/g)

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { getConfig } from "@config";
import { errorResponse, jsonResponse } from "@response";
@ -30,11 +31,61 @@ export default async (
return errorResponse("Method not allowed", 405);
}
const username = matchedRoute.params.username;
const config = getConfig();
try {
if (
config.activitypub.reject_activities.includes(
new URL(req.headers.get("Origin") ?? "").hostname
)
) {
// Discard request
return jsonResponse({});
}
} catch (e) {
console.error(
`[-] Error parsing Origin header of incoming Activity from ${req.headers.get(
"Origin"
)}`
);
console.error(e);
}
// Process request body
const body: APActivity = await req.json();
// Verify HTTP signature
const signature = req.headers.get("Signature") ?? "";
const signatureParams = signature
.split(",")
.reduce<Record<string, string>>((params, param) => {
const [key, value] = param.split("=");
params[key] = value.replace(/"/g, "");
return params;
}, {});
const signedString = `(request-target): post /users/${username}/inbox\nhost: ${
config.http.base_url
}\ndate: ${req.headers.get("Date")}`;
const signatureBuffer = new TextEncoder().encode(signatureParams.signature);
const signatureBytes = new Uint8Array(signatureBuffer).buffer;
const publicKeyBuffer = (body.actor as any).publicKey.publicKeyPem;
const publicKey = await crypto.subtle.importKey(
"spki",
publicKeyBuffer,
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
false,
["verify"]
);
const verified = await crypto.subtle.verify(
{ name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
publicKey,
signatureBytes,
new TextEncoder().encode(signedString)
);
// Get the object's ActivityPub type
const type = body.type;
@ -57,6 +108,24 @@ export default async (
// Replace the object in database with the new provided object
// TODO: Add authentication
try {
if (
config.activitypub.discard_updates.includes(
new URL(req.headers.get("Origin") ?? "").hostname
)
) {
// Discard request
return jsonResponse({});
}
} catch (e) {
console.error(
`[-] Error parsing Origin header of incoming Update Activity from ${req.headers.get(
"Origin"
)}`
);
console.error(e);
}
const object = await RawActivity.updateObjectIfExists(
body.object as APObject
);
@ -78,6 +147,24 @@ export default async (
// Delete the object from database
// TODO: Add authentication
try {
if (
config.activitypub.discard_deletes.includes(
new URL(req.headers.get("Origin") ?? "").hostname
)
) {
// Discard request
return jsonResponse({});
}
} catch (e) {
console.error(
`[-] Error parsing Origin header of incoming Delete Activity from ${req.headers.get(
"Origin"
)}`
);
console.error(e);
}
const response = await RawActivity.deleteObjectIfExists(
body.object as APObject
);
@ -152,6 +239,24 @@ export default async (
// Body is an APFollow object
// Add the actor to the object actor's followers list
try {
if (
config.activitypub.discard_follows.includes(
new URL(req.headers.get("Origin") ?? "").hostname
)
) {
// Reject request
return jsonResponse({});
}
} catch (e) {
console.error(
`[-] Error parsing Origin header of incoming Delete Activity from ${req.headers.get(
"Origin"
)}`
);
console.error(e);
}
const user = await User.getByActorId(
(body.actor as APActor).id ?? ""
);