diff --git a/backend/src/activitypub/activities/handle.ts b/backend/src/activitypub/activities/handle.ts index 7c9c044..b350c21 100644 --- a/backend/src/activitypub/activities/handle.ts +++ b/backend/src/activitypub/activities/handle.ts @@ -345,15 +345,19 @@ export async function handle( // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-delete case 'Delete': { const objectId = getObjectAsId() - - // FIXME: check that Actor is the author of the Note. + const actorId = getActorAsId() const obj = await objects.getObjectByOriginalId(db, objectId) - if (obj === null) { - console.warn('unknown object') + if (obj === null || !obj[originalActorIdSymbol]) { + console.warn('unknown object or missing originalActorId') break } + if (actorId.toString() !== obj[originalActorIdSymbol]) { + console.warn(`authorized Delete (${actorId} vs ${obj[originalActorIdSymbol]})`) + return + } + if (!['Note'].includes(obj.type)) { console.warn('unsupported Update for Object type: ' + activity.object.type) return diff --git a/backend/test/activitypub/handle.spec.ts b/backend/test/activitypub/handle.spec.ts index 0a69e17..4032f37 100644 --- a/backend/test/activitypub/handle.spec.ts +++ b/backend/test/activitypub/handle.spec.ts @@ -664,6 +664,43 @@ describe('ActivityPub', () => { assert.equal(count, 0) }) + test('reject Note deletion from another Actor', async () => { + const db = await makeDB() + const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com') + const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com') + + const originalObjectId = 'https://example.com/note123' + + // ActorB creates a Note + await db + .prepare( + 'INSERT INTO objects (id, type, properties, original_actor_id, original_object_id, local, mastodon_id) VALUES (?, ?, ?, ?, ?, 1, ?)' + ) + .bind( + 'https://example.com/object1', + 'Note', + JSON.stringify({ content: 'my first status' }), + actorB.id.toString(), + originalObjectId, + 'mastodonid1' + ) + .run() + + const activity: any = { + type: 'Delete', + actor: actorA.id, // ActorA attempts to delete + to: [], + cc: [], + object: actorA.id, + } + + await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) + + // Ensure that we didn't actually delete the object + const { count } = await db.prepare('SELECT count(*) as count FROM objects').first<{ count: number }>() + assert.equal(count, 1) + }) + test('ignore deletion of an Actor', async () => { const db = await makeDB() const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')