Skip to content

Commit 3ed2eb8

Browse files
committed
fix(api): enforce federation jsonld context
1 parent de017e6 commit 3ed2eb8

5 files changed

Lines changed: 270 additions & 74 deletions

File tree

packages/api/src/api/contracts.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,26 @@ export type ContainerTaskSnapshot = {
490490
readonly agents: ReadonlyArray<AgentSession>
491491
}
492492

493+
export const activityStreamsJsonLdContext = "https://www.w3.org/ns/activitystreams" as const
494+
export const forgeFedJsonLdContext = "https://forgefed.org/ns" as const
495+
export const securityJsonLdContext = "https://w3id.org/security/v1" as const
496+
export const activityForgeFedJsonLdContext = [
497+
activityStreamsJsonLdContext,
498+
forgeFedJsonLdContext
499+
] as const
500+
export const actorJsonLdContext = [
501+
activityStreamsJsonLdContext,
502+
securityJsonLdContext,
503+
forgeFedJsonLdContext
504+
] as const
505+
export const federationJsonLdContentType =
506+
`application/ld+json; profile="${activityStreamsJsonLdContext}"` as const
507+
export const federationJsonLdResponseContentType =
508+
`${federationJsonLdContentType}; charset=utf-8` as const
509+
510+
export type ActivityForgeFedJsonLdContext = typeof activityForgeFedJsonLdContext
511+
export type ActorJsonLdContext = typeof actorJsonLdContext
512+
493513
export type ForgeFedTicket = {
494514
readonly id: string
495515
readonly attributedTo: string
@@ -550,7 +570,7 @@ export type CreateFollowRequest = {
550570
export type FollowStatus = "pending" | "accepted" | "rejected"
551571

552572
export type ActivityPubFollowActivity = {
553-
readonly "@context": string | ReadonlyArray<string>
573+
readonly "@context": ActivityForgeFedJsonLdContext
554574
readonly id: string
555575
readonly type: "Follow"
556576
readonly actor: string
@@ -566,7 +586,7 @@ export type ActivityPubPublicKey = {
566586
}
567587

568588
export type ActivityPubPerson = {
569-
readonly "@context": "https://www.w3.org/ns/activitystreams"
589+
readonly "@context": ActorJsonLdContext
570590
readonly type: "Person"
571591
readonly id: string
572592
readonly name: string
@@ -584,7 +604,7 @@ export type ActivityPubPerson = {
584604
}
585605

586606
export type ActivityPubOrderedCollection = {
587-
readonly "@context": "https://www.w3.org/ns/activitystreams" | ReadonlyArray<string>
607+
readonly "@context": ActivityForgeFedJsonLdContext
588608
readonly type: "OrderedCollection"
589609
readonly id: string
590610
readonly totalItems: number

packages/api/src/http.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as Schema from "effect/Schema"
1111
import { renderError, type AppError } from "@effect-template/lib/usecases/errors"
1212

1313
import { ApiAuthRequiredError, ApiBadRequestError, ApiConflictError, ApiInternalError, ApiNotFoundError, describeUnknown } from "./api/errors.js"
14-
import type { ApplyProjectRequest } from "./api/contracts.js"
14+
import { federationJsonLdResponseContentType, type ApplyProjectRequest } from "./api/contracts.js"
1515
import {
1616
AuthMenuRequestSchema,
1717
AuthTerminalSessionRequestSchema,
@@ -275,8 +275,8 @@ const binaryResponse = (data: Uint8Array, contentType: string, status = 200) =>
275275
)
276276
)
277277

278-
const activityJsonResponse = (data: unknown, status: number) =>
279-
textResponse(JSON.stringify(data), "application/activity+json; charset=utf-8", status)
278+
const jsonLdResponse = (data: unknown, status: number) =>
279+
textResponse(JSON.stringify(data), federationJsonLdResponseContentType, status)
280280

281281
const parseQueryInt = (url: string, key: string, fallback: number): number => {
282282
const parsed = Number(new URL(url, "http://localhost").searchParams.get(key) ?? "")
@@ -595,6 +595,13 @@ export const federationExchangeStatusResponse = () =>
595595
return yield* _(jsonResponse(makeFederationExchangeStatus(context), 200))
596596
}).pipe(Effect.catchAll(errorResponse))
597597

598+
export const federationActorDocumentResponse = () =>
599+
Effect.gen(function*(_) {
600+
const request = yield* _(HttpServerRequest.HttpServerRequest)
601+
const context = yield* _(resolveFederationContext(request))
602+
return yield* _(jsonLdResponse(makeFederationActorDocument(context), 200))
603+
}).pipe(Effect.catchAll(errorResponse))
604+
598605
const terminalWebSocketUpgradeResponse = Effect.gen(function*(_) {
599606
const request = yield* _(HttpServerRequest.HttpServerRequest)
600607
const upgrade = readHeader(request, "upgrade")?.toLowerCase()
@@ -859,42 +866,38 @@ export const makeRouter = () => {
859866
),
860867
HttpRouter.get(
861868
"/federation/actor",
862-
Effect.gen(function*(_) {
863-
const request = yield* _(HttpServerRequest.HttpServerRequest)
864-
const context = yield* _(resolveFederationContext(request))
865-
return yield* _(activityJsonResponse(makeFederationActorDocument(context), 200))
866-
}).pipe(Effect.catchAll(errorResponse))
869+
federationActorDocumentResponse()
867870
),
868871
HttpRouter.get(
869872
"/federation/outbox",
870873
Effect.gen(function*(_) {
871874
const request = yield* _(HttpServerRequest.HttpServerRequest)
872875
const context = yield* _(resolveFederationContext(request))
873-
return yield* _(activityJsonResponse(makeFederationOutboxCollection(context), 200))
876+
return yield* _(jsonLdResponse(makeFederationOutboxCollection(context), 200))
874877
}).pipe(Effect.catchAll(errorResponse))
875878
),
876879
HttpRouter.get(
877880
"/federation/followers",
878881
Effect.gen(function*(_) {
879882
const request = yield* _(HttpServerRequest.HttpServerRequest)
880883
const context = yield* _(resolveFederationContext(request))
881-
return yield* _(activityJsonResponse(makeFederationFollowersCollection(context), 200))
884+
return yield* _(jsonLdResponse(makeFederationFollowersCollection(context), 200))
882885
}).pipe(Effect.catchAll(errorResponse))
883886
),
884887
HttpRouter.get(
885888
"/federation/following",
886889
Effect.gen(function*(_) {
887890
const request = yield* _(HttpServerRequest.HttpServerRequest)
888891
const context = yield* _(resolveFederationContext(request))
889-
return yield* _(activityJsonResponse(makeFederationFollowingCollection(context), 200))
892+
return yield* _(jsonLdResponse(makeFederationFollowingCollection(context), 200))
890893
}).pipe(Effect.catchAll(errorResponse))
891894
),
892895
HttpRouter.get(
893896
"/federation/liked",
894897
Effect.gen(function*(_) {
895898
const request = yield* _(HttpServerRequest.HttpServerRequest)
896899
const context = yield* _(resolveFederationContext(request))
897-
return yield* _(activityJsonResponse(makeFederationLikedCollection(context), 200))
900+
return yield* _(jsonLdResponse(makeFederationLikedCollection(context), 200))
898901
}).pipe(Effect.catchAll(errorResponse))
899902
),
900903
HttpRouter.get(

0 commit comments

Comments
 (0)