diff --git a/src/activitypub.ts b/src/activitypub.ts index 5e8eba0..8d20219 100644 --- a/src/activitypub.ts +++ b/src/activitypub.ts @@ -1,4 +1,4 @@ -import { authorized, verify } from "./request" +import { authorized, defaultHeaders, verify } from "./request" import outbox from "./outbox" import inbox from "./inbox" import ACTOR from "../actor" @@ -34,15 +34,15 @@ export function reqIsActivityPub(req:Request) { const postOutbox = async (req:Request):Promise => { console.log("PostOutbox") - if(!authorized(req)) return new Response('', { status: 401 }) + if(!authorized(req)) return new Response('', { status: 401, headers: defaultHeaders(req) }) const bodyText = await req.text() const body = JSON.parse(bodyText) // ensure that the verified actor matches the actor in the request body - if (ACTOR.id !== body.actor) return new Response("", { status: 401 }) + if (ACTOR.id !== body.actor) return new Response("", { status: 401, headers: defaultHeaders(req) }) - return await outbox(body, db) + return await outbox(body, req, db) } const postInbox = async (req:Request):Promise => { @@ -56,15 +56,15 @@ const postInbox = async (req:Request):Promise => { // verify the signed HTTP request from = await verify(req, bodyText); } catch (err) { - return new Response("", { status: 401 }) + return new Response("", { status: 401, headers: defaultHeaders(req) }) } const body = JSON.parse(bodyText) // ensure that the verified actor matches the actor in the request body - if (from !== body.actor) return new Response("", { status: 401 }) + if (from !== body.actor) return new Response("", { status: 401, headers: defaultHeaders(req) }) - return await inbox(body, db) + return await inbox(body, req, db) } const getOutbox = async (req:Request):Promise => { @@ -82,7 +82,7 @@ const getOutbox = async (req:Request):Promise => { ...post, actor: ACTOR.id })).sort( (a,b) => new Date(b.published).getTime() - new Date(a.published).getTime()) - }, { headers: { "Content-Type": "application/activity+json"} }) + }, { status:200, headers: defaultHeaders(req) }) } const getFollowers = async (req:Request):Promise => { @@ -98,7 +98,7 @@ const getFollowers = async (req:Request):Promise => { type: "OrderedCollection", totalItems: followers?.length, first: `${ACTOR.followers}?page=1`, - }) + }, { status:200, headers: defaultHeaders(req) }) else return Response.json({ "@context": "https://www.w3.org/ns/activitystreams", id: `${ACTOR.followers}?page=${page}`, @@ -106,7 +106,7 @@ const getFollowers = async (req:Request):Promise => { partOf: ACTOR.followers, totalItems: followers?.length, orderedItems: followers?.map(follower => follower?.id) - }) + }, { status:200, headers: defaultHeaders(req) }) } const getFollowing = async (req:Request):Promise => { @@ -122,7 +122,7 @@ const getFollowing = async (req:Request):Promise => { type: "OrderedCollection", totalItems: following?.length, first: `${ACTOR.following}?page=1`, - }) + }, { status:200, headers: defaultHeaders(req) }) else return Response.json({ "@context": "https://www.w3.org/ns/activitystreams", id: `${ACTOR.following}?page=${page}`, @@ -130,25 +130,25 @@ const getFollowing = async (req:Request):Promise => { partOf: ACTOR.following, totalItems: following?.length, orderedItems: following?.map(follow => follow.id) - }) + }, { status:200, headers: defaultHeaders(req) }) } const getActor = async (req:Request):Promise => { console.log("GetActor") - if(reqIsActivityPub(req)) return Response.json(ACTOR, { headers: { "Content-Type": "application/activity+json"}}) - else return Response.json(db.listPosts()) + if(reqIsActivityPub(req)) return Response.json(ACTOR, { status:200, headers: defaultHeaders(req) }) + else return Response.json(db.listPosts(), { status:200, headers: defaultHeaders(req) }) } const getPost = async (req:Request, id:string):Promise => { console.log("GetPost", id) - if(reqIsActivityPub(req)) return Response.json((db.getOutboxActivity(id))?.object, { headers: { "Content-Type": "application/activity+json"}}) - else return Response.json(db.getPost(id)) + if(reqIsActivityPub(req)) return Response.json((db.getOutboxActivity(id))?.object, { status:200, headers: defaultHeaders(req) }) + else return Response.json(db.getPost(id), { status:200, headers: defaultHeaders(req) }) } const getOutboxActivity = async (req:Request, id:string):Promise => { console.log("GetOutboxActivity", id) - return Response.json((db.getOutboxActivity(id)), { headers: { "Content-Type": "application/activity+json"}}) + return Response.json((db.getOutboxActivity(id)), { status:200, headers: defaultHeaders(req) }) } \ No newline at end of file diff --git a/src/inbox.ts b/src/inbox.ts index bc0e3cc..7a5f006 100644 --- a/src/inbox.ts +++ b/src/inbox.ts @@ -1,10 +1,10 @@ import outbox from "./outbox" -import { idsFromValue, send } from "./request" +import { defaultHeaders, idsFromValue, send } from "./request" import ACTOR from "../actor" import ActivityPubDB from "./db" let db:ActivityPubDB -export default async function inbox(activity:any, database:ActivityPubDB) { +export default async function inbox(activity:any, req:Request, database:ActivityPubDB) { db = database const date = new Date() // get the main recipients ([...new Set()] is to dedupe) @@ -22,16 +22,16 @@ export default async function inbox(activity:any, database:ActivityPubDB) { // TODO: process the activity and update local data switch(activity.type) { - case "Follow": follow(activity, activity_id); break; + case "Follow": follow(activity, activity_id, req); break; case "Accept": accept(activity); break; case "Reject": reject(activity); break; case "Undo": undo(activity); break; } - return new Response("", { status: 204 }) + return new Response("", { status:204, headers: defaultHeaders(req) }) } -const follow = async (activity:any, activity_id:string) => { +const follow = async (activity:any, activity_id:string, req:Request) => { // someone is following me // save this follower locally db.createFollower(activity_id, activity.actor, activity.published || new Date().toISOString()) @@ -42,7 +42,7 @@ const follow = async (activity:any, activity_id:string) => { actor: ACTOR.id, to: [activity.actor], object: activity, - }, db); + }, req, db); } const undo = async (activity:any) => { diff --git a/src/outbox.ts b/src/outbox.ts index d73dd74..34c2373 100644 --- a/src/outbox.ts +++ b/src/outbox.ts @@ -1,10 +1,10 @@ -import { fetchObject, idsFromValue, send } from "./request" +import { defaultHeaders, fetchObject, idsFromValue, send } from "./request" import ACTOR from "../actor" import ActivityPubDB, { Activity } from "./db" let db:ActivityPubDB -export default async function outbox(activity:any, database:ActivityPubDB):Promise { +export default async function outbox(activity:any, req:Request, database:ActivityPubDB):Promise { db = database const date = new Date() const activity_id = `${date.getTime().toString(32)}` @@ -60,7 +60,7 @@ export default async function outbox(activity:any, database:ActivityPubDB):Promi else if (to) send(to, activity) }) - return new Response("", { status: 201, headers: { location: activity.id } }) + return new Response("", { status: 201, headers: { ...defaultHeaders(req), location: activity.id } }) } async function outboxSideEffect(activity_id:string, activity:Activity) { diff --git a/src/request.ts b/src/request.ts index 1e7ece3..9f12835 100644 --- a/src/request.ts +++ b/src/request.ts @@ -10,6 +10,21 @@ export function idsFromValue(value:any):string[] { return [] } +export function defaultHeaders(req:Request) { + const headers:any = { + 'Access-Control-Allow-Origin': req.headers.get('Origin') || '*', + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, DELETE', + 'Access-Control-Max-Age': '86400', + 'Access-Control-Allow-Credentials': true, + 'Content-Type': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' + } + let h + if (h = req.headers.get('Access-Control-Request-Headers')) + headers['Access-Control-Allow-Headers'] = 'Accept, Content-Type, Authorization, Signature, Digest, Date, Host' + + return headers +} + // this function adds / modifies the appropriate headers for signing a request, then calls fetch export function signedFetch(url: string | URL | Request, init?: FetchRequestInit): Promise {