Skip to content

Commit

Permalink
InboxContext.recipient property
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Oct 31, 2024
1 parent 8cbbc94 commit aee5b1a
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 48 deletions.
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Version 1.2.0

To be released.

- Added `InboxContext.recipient` property.

- Added NodeInfo client functions.

- Added `getNodeInfo()` function.
Expand Down
7 changes: 7 additions & 0 deletions src/federation/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,13 @@ export interface RequestContext<TContextData> extends Context<TContextData> {
* @since 1.0.0
*/
export interface InboxContext<TContextData> extends Context<TContextData> {
/**
* The identifier of the recipient of the inbox. If the inbox is a shared
* inbox, it is `null`.
* @since 1.2.0
*/
recipient: string | null;

/**
* Forwards a received activity to the recipients' inboxes. The forwarded
* activity will be signed in HTTP Signatures by the forwarder, but its
Expand Down
24 changes: 12 additions & 12 deletions src/federation/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,7 @@ test("handleInbox()", async () => {
skipSignatureVerification: false,
} as const;
let response = await handleInbox(unsignedRequest, {
identifier: null,
recipient: null,
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext(unsignedContext);
Expand All @@ -1112,10 +1112,10 @@ test("handleInbox()", async () => {

onNotFoundCalled = null;
response = await handleInbox(unsignedRequest, {
identifier: "nobody",
recipient: "nobody",
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext(unsignedContext);
return createInboxContext({ ...unsignedContext, recipient: "nobody" });
},
...inboxOptions,
});
Expand All @@ -1124,7 +1124,7 @@ test("handleInbox()", async () => {

onNotFoundCalled = null;
response = await handleInbox(unsignedRequest, {
identifier: null,
recipient: null,
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext(unsignedContext);
Expand All @@ -1135,10 +1135,10 @@ test("handleInbox()", async () => {
assertEquals(response.status, 401);

response = await handleInbox(unsignedRequest, {
identifier: "someone",
recipient: "someone",
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext(unsignedContext);
return createInboxContext({ ...unsignedContext, recipient: "someone" });
},
...inboxOptions,
});
Expand All @@ -1158,7 +1158,7 @@ test("handleInbox()", async () => {
documentLoader: mockDocumentLoader,
});
response = await handleInbox(signedRequest, {
identifier: null,
recipient: null,
context: signedContext,
inboxContextFactory(_activity) {
return createInboxContext(unsignedContext);
Expand All @@ -1169,18 +1169,18 @@ test("handleInbox()", async () => {
assertEquals(response.status, 202);

response = await handleInbox(signedRequest, {
identifier: "someone",
recipient: "someone",
context: signedContext,
inboxContextFactory(_activity) {
return createInboxContext(unsignedContext);
return createInboxContext({ ...unsignedContext, recipient: "someone" });
},
...inboxOptions,
});
assertEquals(onNotFoundCalled, null);
assertEquals(response.status, 202);

response = await handleInbox(unsignedRequest, {
identifier: null,
recipient: null,
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext(unsignedContext);
Expand All @@ -1192,10 +1192,10 @@ test("handleInbox()", async () => {
assertEquals(response.status, 202);

response = await handleInbox(unsignedRequest, {
identifier: "someone",
recipient: "someone",
context: unsignedContext,
inboxContextFactory(_activity) {
return createInboxContext(unsignedContext);
return createInboxContext({ ...unsignedContext, recipient: "someone" });
},
...inboxOptions,
skipSignatureVerification: true,
Expand Down
61 changes: 37 additions & 24 deletions src/federation/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,10 @@ function filterCollectionItems<TItem extends Object | Link | Recipient | URL>(
}

export interface InboxHandlerParameters<TContextData> {
identifier: string | null;
recipient: string | null;
context: RequestContext<TContextData>;
inboxContextFactory(
recipient: string | null,
activity: unknown,
): InboxContext<TContextData>;
kv: KvStore;
Expand All @@ -363,7 +364,7 @@ export interface InboxHandlerParameters<TContextData> {
export async function handleInbox<TContextData>(
request: Request,
{
identifier,
recipient,
context,
inboxContextFactory,
kv,
Expand All @@ -379,26 +380,26 @@ export async function handleInbox<TContextData>(
): Promise<Response> {
const logger = getLogger(["fedify", "federation", "inbox"]);
if (actorDispatcher == null) {
logger.error("Actor dispatcher is not set.", { identifier });
logger.error("Actor dispatcher is not set.", { recipient });
return await onNotFound(request);
} else if (identifier != null) {
const actor = await actorDispatcher(context, identifier);
} else if (recipient != null) {
const actor = await actorDispatcher(context, recipient);
if (actor == null) {
logger.error("Actor {identifier} not found.", { identifier });
logger.error("Actor {recipient} not found.", { recipient });
return await onNotFound(request);
}
}
let json: unknown;
try {
json = await request.clone().json();
} catch (error) {
logger.error("Failed to parse JSON:\n{error}", { identifier, error });
logger.error("Failed to parse JSON:\n{error}", { recipient, error });
try {
await inboxErrorHandler?.(context, error as Error);
} catch (error) {
logger.error(
"An unexpected error occurred in inbox error handler:\n{error}",
{ error, activity: json },
{ error, activity: json, recipient },
);
}
return new Response("Invalid JSON.", {
Expand Down Expand Up @@ -437,12 +438,12 @@ export async function handleInbox<TContextData>(
const jsonWithoutSig = detachSignature(json);
let activity: Activity | null = null;
if (ldSigVerified) {
logger.debug("Linked Data Signatures are verified.", { identifier, json });
logger.debug("Linked Data Signatures are verified.", { recipient, json });
activity = await Activity.fromJsonLd(jsonWithoutSig, context);
} else {
logger.debug(
"Linked Data Signatures are not verified.",
{ identifier, json },
{ recipient, json },
);
try {
activity = await verifyObject(Activity, jsonWithoutSig, {
Expand All @@ -452,16 +453,16 @@ export async function handleInbox<TContextData>(
});
} catch (error) {
logger.error("Failed to parse activity:\n{error}", {
identifier,
json,
recipient,
activity: json,
error,
});
try {
await inboxErrorHandler?.(context, error as Error);
} catch (error) {
logger.error(
"An unexpected error occurred in inbox error handler:\n{error}",
{ error, activity: json },
{ error, activity: json, recipient },
);
}
return new Response("Invalid activity.", {
Expand All @@ -472,12 +473,12 @@ export async function handleInbox<TContextData>(
if (activity == null) {
logger.debug(
"Object Integrity Proofs are not verified.",
{ identifier, json },
{ recipient, activity: json },
);
} else {
logger.debug(
"Object Integrity Proofs are verified.",
{ identifier, json },
{ recipient, activity: json },
);
}
}
Expand All @@ -493,7 +494,7 @@ export async function handleInbox<TContextData>(
if (key == null) {
logger.error(
"Failed to verify the request's HTTP Signatures.",
{ identifier },
{ recipient },
);
const response = new Response(
"Failed to verify the request signature.",
Expand All @@ -504,7 +505,7 @@ export async function handleInbox<TContextData>(
);
return response;
} else {
logger.debug("HTTP Signatures are verified.", { identifier });
logger.debug("HTTP Signatures are verified.", { recipient });
}
httpSigKey = key;
}
Expand All @@ -519,6 +520,7 @@ export async function handleInbox<TContextData>(
logger.debug("Activity {activityId} has already been processed.", {
activityId: activity.id?.href,
activity: json,
recipient,
});
return new Response(
`Activity <${activity.id}> has already been processed.`,
Expand All @@ -544,6 +546,7 @@ export async function handleInbox<TContextData>(
"The signer ({keyId}) and the actor ({actorId}) do not match.",
{
activity: json,
recipient,
keyId: httpSigKey.id?.href,
actorId: activity.actorId.href,
},
Expand All @@ -561,14 +564,14 @@ export async function handleInbox<TContextData>(
id: crypto.randomUUID(),
baseUrl: request.url,
activity: json,
identifier,
identifier: recipient,
attempt: 0,
started: new Date().toISOString(),
} satisfies InboxMessage,
);
logger.info(
"Activity {activityId} is enqueued.",
{ activityId: activity.id?.href, activity: json },
{ activityId: activity.id?.href, activity: json, recipient },
);
return new Response("Activity is enqueued.", {
status: 202,
Expand All @@ -579,27 +582,37 @@ export async function handleInbox<TContextData>(
if (listener == null) {
logger.error(
"Unsupported activity type:\n{activity}",
{ activity: json },
{ activity: json, recipient },
);
return new Response("", {
status: 202,
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
}
try {
await listener(inboxContextFactory(json), activity);
await listener(inboxContextFactory(recipient, json), activity);
} catch (error) {
try {
await inboxErrorHandler?.(context, error as Error);
} catch (error) {
logger.error(
"An unexpected error occurred in inbox error handler:\n{error}",
{ error, activityId: activity.id?.href, activity: json },
{
error,
activityId: activity.id?.href,
activity: json,
recipient,
},
);
}
logger.error(
"Failed to process the incoming activity {activityId}:\n{error}",
{ error, activityId: activity.id?.href, activity: json },
{
error,
activityId: activity.id?.href,
activity: json,
recipient,
},
);
return new Response("Internal server error.", {
status: 500,
Expand All @@ -611,7 +624,7 @@ export async function handleInbox<TContextData>(
}
logger.info(
"Activity {activityId} has been processed.",
{ activityId: activity.id?.href, activity: json },
{ activityId: activity.id?.href, activity: json, recipient },
);
return new Response("", {
status: 202,
Expand Down
7 changes: 4 additions & 3 deletions src/federation/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1217,7 +1217,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => {
"id": "https://example.com/activity",
"actor": "https://example.com/person2",
};
const ctx = new InboxContextImpl(activity, {
const ctx = new InboxContextImpl(null, activity, {
data: undefined,
federation,
url: new URL("https://example.com/"),
Expand All @@ -1241,7 +1241,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => {
"id": "https://example.com/activity",
"actor": "https://example.com/person2",
};
const ctx = new InboxContextImpl(activity, {
const ctx = new InboxContextImpl(null, activity, {
data: undefined,
federation,
url: new URL("https://example.com/"),
Expand Down Expand Up @@ -1270,6 +1270,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => {
{ contextLoader: mockDocumentLoader, documentLoader: mockDocumentLoader },
);
const ctx = new InboxContextImpl(
null,
await activity.toJsonLd({ contextLoader: mockDocumentLoader }),
{
data: undefined,
Expand Down Expand Up @@ -1301,7 +1302,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => {
rsaPublicKey3.id!,
{ contextLoader: mockDocumentLoader },
);
const ctx = new InboxContextImpl(activity, {
const ctx = new InboxContextImpl(null, activity, {
data: undefined,
federation,
url: new URL("https://example.com/"),
Expand Down
Loading

0 comments on commit aee5b1a

Please sign in to comment.