mirror of
https://github.com/versia-pub/server.git
synced 2026-03-13 13:59:16 +01:00
Refactor configs and activitypub parts
This commit is contained in:
parent
ca7d325cb1
commit
c0ff46559b
17 changed files with 251 additions and 70 deletions
|
|
@ -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`
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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 ?? ""
|
||||
);
|
||||
Loading…
Add table
Add a link
Reference in a new issue