diff --git a/lib/activitypub/federate.ts b/lib/activitypub/federate.ts index a271cbd..f3ff330 100644 --- a/lib/activitypub/federate.ts +++ b/lib/activitypub/federate.ts @@ -24,7 +24,7 @@ function createActivity(article: Article): Create { return createObject; } -export async function getActor(url: string, db: Database, forceUpdate: boolean = false): Promise { +export async function getActor(url: string, db: Database, forceUpdate: boolean = false): Promise { if (!forceUpdate) { try { const cached = await getCachedActor(url, db); @@ -34,7 +34,7 @@ export async function getActor(url: string, db: Database, forceUpdate: boolean = } } const remote = await fetchActor(url); - cacheActor(remote, db); + if (remote) cacheActor(remote, db); return remote; } @@ -80,7 +80,7 @@ async function cacheActor(actor: Actor, db: Database) { }); } -async function fetchActor(url: string): Promise { +async function fetchActor(url: string): Promise { return new Promise((resolve, reject) => { request({ url, @@ -91,7 +91,7 @@ async function fetchActor(url: string): Promise { json: true }, (err, res) => { if (err) reject(err); - else resolve(res.body as Actor); + else resolve(res.body ? res.body as Actor : null); }); }); } diff --git a/lib/activitypub/inbox.ts b/lib/activitypub/inbox.ts index 6a9a6c3..143cf48 100644 --- a/lib/activitypub/inbox.ts +++ b/lib/activitypub/inbox.ts @@ -41,6 +41,11 @@ async function handleFollow(activity: Activity, req: Request, res: Response) { } const db = req.app.get("db") as Database; const actor = await getActor(follow.actor, db, true); // always force re-fetch the actor on follow + if (!actor) { + // if the actor ceases existing between the time the Follow is sent and when receive it, ignore it and end the request + res.end(); + return; + } const acceptObject = { "@context": [ "https://www.w3.org/ns/activitystreams", diff --git a/lib/activitypub/middleware/http-signature.ts b/lib/activitypub/middleware/http-signature.ts index 77020da..0fa9b10 100644 --- a/lib/activitypub/middleware/http-signature.ts +++ b/lib/activitypub/middleware/http-signature.ts @@ -11,16 +11,28 @@ export = async (req: Request, res: Response, next: NextFunction) => { } const db = req.app.get("db") as Database; const actor = await getActor(req.body.actor as string, db); - if (validate(req, actor.publicKey.publicKeyPem)) { + if (actor && validate(req, actor.publicKey.publicKeyPem)) { next(); } else { // if the first check fails, force re-fetch the actor and try again const actor = await getActor(req.body.actor as string, db, true); - if (validate(req, actor.publicKey.publicKeyPem)) { - next(); - } else { + if (!actor) { + // probably caused by Delete activity for an actor + if (req.body.type === "Delete") { + // if we don't have a cached copy of the key, we have had no interaction with actor + // so the Delete can be safely ignored + // we still send a 200 OK status, so that the originating instances knows it has successfully + // delivered the Delete (we just can't act on it) + res.status(200).end(); + } else { + console.log(`Could not retrieve actor ${req.body.actor} to validate HTTP signature for`, req.body); + res.status(401).end("Could not retrieve actor to validate HTTP signature"); + } + } else if (!validate(req, actor.publicKey.publicKeyPem)) { console.log(`Could not validate HTTP signature for ${req.body.actor}`); res.status(401).end("Could not validate HTTP signature"); + } else { + next(); } } };