Skip to content

Commit f4126bc

Browse files
committed
Harden fixed-path actor aliases and address review feedback
Harden `mapActorAlias()` by adding a duplicate registration guard and using a constant for the alias prefix. Updated JSDoc to include the `RouterError` contract. Improved test coverage with a duplicate alias registration test and added a null-guard for WebFinger aliases in the middleware test. Also added a title to `pr-description.md`. #753 (comment) #753 (comment) #753 (comment) #753 (comment) #753 (comment) #753 (comment) Assisted-by: Gemini CLI:gemini-3-flash-preview Assisted-by: Codex:gpt-5.5
1 parent ca15ae2 commit f4126bc

6 files changed

Lines changed: 31 additions & 7 deletions

File tree

packages/fedify/src/federation/builder.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ test("FederationBuilder", async (t) => {
3535
RouterError,
3636
"Path for actor alias must have no variables.",
3737
);
38+
assertThrows(
39+
() =>
40+
createFederationBuilder<string>()
41+
.setActorDispatcher("/users/{identifier}", actorDispatcher)
42+
.mapActorAlias("/actor", "instance")
43+
.mapActorAlias("/bot", "instance"),
44+
RouterError,
45+
'Actor alias for "instance" already set.',
46+
);
3847
builder.setActorDispatcher("/users/{identifier}", actorDispatcher)
3948
.mapActorAlias("/actor", "instance");
4049

packages/fedify/src/federation/builder.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ import type {
6363
} from "./handler.ts";
6464
import { Router, RouterError } from "./router.ts";
6565

66+
const ACTOR_ALIAS_PREFIX = "actorAlias:";
67+
6668
function validateSingleIdentifierVariablePath(
6769
path: string,
6870
errorMessage: string,
@@ -523,13 +525,18 @@ export class FederationBuilderImpl<TContextData>
523525
return setters;
524526
},
525527
mapActorAlias: (path: string, identifier: string) => {
528+
if (this.router.has(`${ACTOR_ALIAS_PREFIX}${identifier}`)) {
529+
throw new RouterError(
530+
`Actor alias for "${identifier}" already set.`,
531+
);
532+
}
526533
const variables = new Router().add(path, "temp");
527534
if (variables.size > 0) {
528535
throw new RouterError(
529536
"Path for actor alias must have no variables.",
530537
);
531538
}
532-
this.router.add(path, `actorAlias:${identifier}`);
539+
this.router.add(path, `${ACTOR_ALIAS_PREFIX}${identifier}`);
533540
return setters;
534541
},
535542
authorize(predicate: AuthorizePredicate<TContextData>) {

packages/fedify/src/federation/federation.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,6 +1115,8 @@ export interface ActorCallbackSetters<TContextData> {
11151115
* @param path The fixed path to map to the identifier.
11161116
* @param identifier The sentinel identifier to map the path to.
11171117
* @returns The setters object so that settings can be chained.
1118+
* @throws {RouterError} If the provided path or identifier is invalid or fails
1119+
* runtime validation.
11181120
* @since 2.3.0
11191121
*/
11201122
mapActorAlias(

packages/fedify/src/federation/middleware.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,7 @@ test("Federation.fetch()", async (t) => {
12191219
);
12201220
assertExists(selfLink);
12211221
assertEquals(selfLink.href, "https://example.com/bot");
1222+
assertExists(body.aliases);
12221223
assert((body.aliases as string[]).includes("https://example.com/bot"));
12231224
});
12241225

packages/fedify/src/federation/middleware.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ import type {
112112
} from "./queue.ts";
113113
import { createExponentialBackoffPolicy, type RetryPolicy } from "./retry.ts";
114114
import { RouterError } from "./router.ts";
115+
116+
const ACTOR_ALIAS_PREFIX = "actorAlias:";
115117
import {
116118
extractInboxes,
117119
sendActivity,
@@ -1437,8 +1439,8 @@ export class FederationImpl<TContextData>
14371439
switch (routeName) {
14381440
case "actor":
14391441
case "actorAlias": {
1440-
const identifier = route.name.startsWith("actorAlias:")
1441-
? route.name.substring("actorAlias:".length)
1442+
const identifier = route.name.startsWith(ACTOR_ALIAS_PREFIX)
1443+
? route.name.substring(ACTOR_ALIAS_PREFIX.length)
14421444
: route.values.identifier;
14431445
context = this.#createContext(request, contextData, {
14441446
invokedFromActorDispatcher: { identifier },
@@ -1793,7 +1795,7 @@ export class ContextImpl<TContextData> implements Context<TContextData> {
17931795

17941796
getActorUri(identifier: string): URL {
17951797
let path = this.federation.router.build(
1796-
`actorAlias:${identifier}`,
1798+
`${ACTOR_ALIAS_PREFIX}${identifier}`,
17971799
{},
17981800
);
17991801
if (path == null) {
@@ -1947,10 +1949,10 @@ export class ContextImpl<TContextData> implements Context<TContextData> {
19471949
identifier: undefined,
19481950
};
19491951
}
1950-
const identifier = route.name.startsWith("actorAlias:")
1951-
? route.name.substring("actorAlias:".length)
1952+
const identifier = route.name.startsWith(ACTOR_ALIAS_PREFIX)
1953+
? route.name.substring(ACTOR_ALIAS_PREFIX.length)
19521954
: route.values.identifier;
1953-
if (route.name === "actor" || route.name.startsWith("actorAlias:")) {
1955+
if (route.name === "actor" || route.name.startsWith(ACTOR_ALIAS_PREFIX)) {
19541956
return {
19551957
type: "actor",
19561958
identifier,

pr-description.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
Support fixed-path actor dispatchers (`mapActorAlias()`)
2+
========================================================
3+
14
This PR adds `mapActorAlias()` to `ActorCallbackSetters`. An actor dispatcher
25
can now map a fixed path, such as `/actor` or `/bot`, to an internal sentinel
36
identifier.

0 commit comments

Comments
 (0)