feat: FEP-8fcf/fe34 compliance, custom emoji, manual follow approval (v2.13.0)

- FEP-8fcf: add syncCollection to Undo(Announce) sendActivity
- FEP-fe34: centralized lookupWithSecurity() helper with crossOrigin: "ignore" on all 23 lookupObject call sites
- Custom emoji: replaceCustomEmoji() renders :shortcode: as inline <img> in content and actor display names
- Manual follow approval: profile toggle, ap_pending_follows collection, approve/reject controllers with federation, pending tab on followers page, follow_request notification type
- Coverage audit updated to v2.12.x (overall ~70% → ~82%)

Confab-Link: http://localhost:8080/sessions/1f1e729b-0087-499e-a991-f36f46211fe4
This commit is contained in:
Ricardo
2026-03-17 08:21:36 +01:00
parent 0c84913ac7
commit 9a61145d97
24 changed files with 656 additions and 92 deletions

View File

@@ -2,6 +2,7 @@ import express from "express";
import { setupFederation, buildPersonActor } from "./lib/federation-setup.js";
import { initRedisCache } from "./lib/redis-cache.js";
import { lookupWithSecurity } from "./lib/lookup-helpers.js";
import {
createFedifyMiddleware,
} from "./lib/federation-bridge.js";
@@ -39,6 +40,10 @@ import {
filterModeController,
} from "./lib/controllers/moderation.js";
import { followersController } from "./lib/controllers/followers.js";
import {
approveFollowController,
rejectFollowController,
} from "./lib/controllers/follow-requests.js";
import { followingController } from "./lib/controllers/following.js";
import { activitiesController } from "./lib/controllers/activities.js";
import {
@@ -304,6 +309,8 @@ export default class ActivityPubEndpoint {
router.post("/admin/reader/block", blockController(mp, this));
router.post("/admin/reader/unblock", unblockController(mp, this));
router.get("/admin/followers", followersController(mp));
router.post("/admin/followers/approve", approveFollowController(mp, this));
router.post("/admin/followers/reject", rejectFollowController(mp, this));
router.get("/admin/following", followingController(mp));
router.get("/admin/activities", activitiesController(mp));
router.get("/admin/featured", featuredGetController(mp));
@@ -493,7 +500,7 @@ export default class ActivityPubEndpoint {
let replyToActor = null;
if (properties["in-reply-to"]) {
try {
const remoteObject = await ctx.lookupObject(
const remoteObject = await lookupWithSecurity(ctx,
new URL(properties["in-reply-to"]),
);
if (remoteObject && typeof remoteObject.getAttributedTo === "function") {
@@ -525,7 +532,7 @@ export default class ActivityPubEndpoint {
for (const { handle } of mentionHandles) {
try {
const mentionedActor = await ctx.lookupObject(
const mentionedActor = await lookupWithSecurity(ctx,
new URL(`acct:${handle}`),
);
if (mentionedActor?.id) {
@@ -701,7 +708,7 @@ export default class ActivityPubEndpoint {
const documentLoader = await ctx.getDocumentLoader({
identifier: handle,
});
const remoteActor = await ctx.lookupObject(actorUrl, {
const remoteActor = await lookupWithSecurity(ctx,actorUrl, {
documentLoader,
});
if (!remoteActor) {
@@ -802,7 +809,7 @@ export default class ActivityPubEndpoint {
const documentLoader = await ctx.getDocumentLoader({
identifier: handle,
});
const remoteActor = await ctx.lookupObject(actorUrl, {
const remoteActor = await lookupWithSecurity(ctx,actorUrl, {
documentLoader,
});
if (!remoteActor) {
@@ -1115,6 +1122,8 @@ export default class ActivityPubEndpoint {
Indiekit.addCollection("ap_explore_tabs");
// Reports collection
Indiekit.addCollection("ap_reports");
// Pending follow requests (manual approval)
Indiekit.addCollection("ap_pending_follows");
// Store collection references (posts resolved lazily)
const indiekitCollections = Indiekit.collections;
@@ -1140,6 +1149,8 @@ export default class ActivityPubEndpoint {
ap_explore_tabs: indiekitCollections.get("ap_explore_tabs"),
// Reports collection
ap_reports: indiekitCollections.get("ap_reports"),
// Pending follow requests (manual approval)
ap_pending_follows: indiekitCollections.get("ap_pending_follows"),
get posts() {
return indiekitCollections.get("posts");
},
@@ -1331,6 +1342,15 @@ export default class ActivityPubEndpoint {
{ reportedUrls: 1 },
{ background: true },
);
// Pending follow requests — unique on actorUrl
this._collections.ap_pending_follows.createIndex(
{ actorUrl: 1 },
{ unique: true, background: true },
);
this._collections.ap_pending_follows.createIndex(
{ requestedAt: -1 },
{ background: true },
);
} catch {
// Index creation failed — collections not yet available.
// Indexes already exist from previous startups; non-fatal.
@@ -1375,7 +1395,7 @@ export default class ActivityPubEndpoint {
const documentLoader = await ctx.getDocumentLoader({
identifier: handle,
});
const actor = await ctx.lookupObject(new URL(actorUrl), {
const actor = await lookupWithSecurity(ctx,new URL(actorUrl), {
documentLoader,
});
if (!actor) return "";