From 196d6fa8960f6a8068c9e59d42c7dac49fef3437 Mon Sep 17 00:00:00 2001 From: Sven Sauleau Date: Mon, 16 Jan 2023 14:16:54 +0000 Subject: [PATCH] MOW-101: hide DM from public --- backend/src/activitypub/activities/handle.ts | 13 +- backend/src/activitypub/activities/index.ts | 2 + backend/src/activitypub/actors/outbox.ts | 17 +- backend/src/activitypub/objects/note.ts | 35 ++- backend/src/mastodon/timeline.ts | 3 + backend/test/activitypub.spec.ts | 44 ++- backend/test/activitypub/handle.spec.ts | 281 ++++++++++-------- backend/test/mastodon/statuses.spec.ts | 4 +- backend/test/mastodon/timelines.spec.ts | 36 ++- functions/ap/users/[id]/outbox/page.ts | 12 +- migrations/0002_add-target-outbox_objects.sql | 4 + 11 files changed, 301 insertions(+), 150 deletions(-) create mode 100644 migrations/0002_add-target-outbox_objects.sql diff --git a/backend/src/activitypub/activities/handle.ts b/backend/src/activitypub/activities/handle.ts index 1d617f4..95c97d8 100644 --- a/backend/src/activitypub/activities/handle.ts +++ b/backend/src/activitypub/activities/handle.ts @@ -1,4 +1,5 @@ import * as actors from 'wildebeest/backend/src/activitypub/actors' +import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities' import type { JWK } from 'wildebeest/backend/src/webpush/jwk' import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox' import { actorURL } from 'wildebeest/backend/src/activitypub/actors' @@ -131,11 +132,17 @@ export async function handle( // FIXME: download any attachment Objects let recipients: Array = [] + let target = PUBLIC_GROUP - if (Array.isArray(activity.to)) { + if (Array.isArray(activity.to) && activity.to.length > 0) { recipients = [...recipients, ...activity.to] + + if (activity.to.length !== 1) { + console.warn("multiple `Activity.to` isn't supported") + } + target = activity.to[0] } - if (Array.isArray(activity.cc)) { + if (Array.isArray(activity.cc) && activity.cc.length > 0) { recipients = [...recipients, ...activity.cc] } @@ -172,7 +179,7 @@ export async function handle( const fromActor = await actors.getAndCache(getActorAsId(), db) // Add the object in the originating actor's outbox, allowing other // actors on this instance to see the note in their timelines. - await addObjectInOutbox(db, fromActor, obj, activity.published) + await addObjectInOutbox(db, fromActor, obj, activity.published, target) for (let i = 0, len = recipients.length; i < len; i++) { const handle = parseHandle(extractID(domain, recipients[i])) diff --git a/backend/src/activitypub/activities/index.ts b/backend/src/activitypub/activities/index.ts index 39cadd3..e9fc8fb 100644 --- a/backend/src/activitypub/activities/index.ts +++ b/backend/src/activitypub/activities/index.ts @@ -1,5 +1,7 @@ export type Activity = any +export const PUBLIC_GROUP = 'https://www.w3.org/ns/activitystreams#Public' + // Generate a unique ID. Note that currently the generated URL aren't routable. export function uri(domain: string): URL { const id = crypto.randomUUID() diff --git a/backend/src/activitypub/actors/outbox.ts b/backend/src/activitypub/actors/outbox.ts index c755cdd..ff67e0c 100644 --- a/backend/src/activitypub/actors/outbox.ts +++ b/backend/src/activitypub/actors/outbox.ts @@ -2,20 +2,27 @@ import type { Object } from 'wildebeest/backend/src/activitypub/objects' import type { Activity } from 'wildebeest/backend/src/activitypub/activities' import type { Actor } from 'wildebeest/backend/src/activitypub/actors' import type { OrderedCollection, OrderedCollectionPage } from 'wildebeest/backend/src/activitypub/core' +import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities' -export async function addObjectInOutbox(db: D1Database, actor: Actor, obj: Object, published_date?: string) { +export async function addObjectInOutbox( + db: D1Database, + actor: Actor, + obj: Object, + published_date?: string, + target: string = PUBLIC_GROUP +) { const id = crypto.randomUUID() let out: any = null if (published_date !== undefined) { out = await db - .prepare('INSERT INTO outbox_objects(id, actor_id, object_id, published_date) VALUES(?, ?, ?, ?)') - .bind(id, actor.id.toString(), obj.id.toString(), published_date) + .prepare('INSERT INTO outbox_objects(id, actor_id, object_id, published_date, target) VALUES(?, ?, ?, ?, ?)') + .bind(id, actor.id.toString(), obj.id.toString(), published_date, target) .run() } else { out = await db - .prepare('INSERT INTO outbox_objects(id, actor_id, object_id) VALUES(?, ?, ?)') - .bind(id, actor.id.toString(), obj.id.toString()) + .prepare('INSERT INTO outbox_objects(id, actor_id, object_id, target) VALUES(?, ?, ?, ?)') + .bind(id, actor.id.toString(), obj.id.toString(), target) .run() } if (!out.success) { diff --git a/backend/src/activitypub/objects/note.ts b/backend/src/activitypub/objects/note.ts index c2e908d..04d6dc5 100644 --- a/backend/src/activitypub/objects/note.ts +++ b/backend/src/activitypub/objects/note.ts @@ -3,10 +3,10 @@ import type { Actor } from 'wildebeest/backend/src/activitypub/actors' import type { Document } from 'wildebeest/backend/src/activitypub/objects' import { followersURL } from 'wildebeest/backend/src/activitypub/actors' +import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities' import * as objects from '.' const NOTE = 'Note' -export const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public' // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-note export interface Note extends objects.Object { @@ -34,7 +34,7 @@ export async function createPublicNote( const properties = { attributedTo: actorId, content, - to: [PUBLIC], + to: [PUBLIC_GROUP], cc: [followersURL(actorId)], // FIXME: stub values @@ -50,3 +50,34 @@ export async function createPublicNote( return (await objects.createObject(domain, db, NOTE, properties, actorId, true)) as Note } + +export async function createPrivateNote( + domain: string, + db: D1Database, + content: string, + actor: Actor, + targetActor: Actor, + attachment: Array = [], + extraProperties: any = {} +): Promise { + const actorId = new URL(actor.id) + + const properties = { + attributedTo: actorId, + content, + to: [targetActor.id.toString()], + cc: [], + + // FIXME: stub values + inReplyTo: null, + replies: null, + sensitive: false, + summary: null, + tag: [], + attachment, + + ...extraProperties, + } + + return (await objects.createObject(domain, db, NOTE, properties, actorId, true)) as Note +} diff --git a/backend/src/mastodon/timeline.ts b/backend/src/mastodon/timeline.ts index 9827748..c735409 100644 --- a/backend/src/mastodon/timeline.ts +++ b/backend/src/mastodon/timeline.ts @@ -2,6 +2,7 @@ import type { MastodonStatus } from 'wildebeest/backend/src/types/status' import { getFollowingId } from 'wildebeest/backend/src/mastodon/follow' import type { Actor } from 'wildebeest/backend/src/activitypub/actors/' import { toMastodonStatusFromRow } from './status' +import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities' export async function pregenerateTimelines(domain: string, db: D1Database, cache: KVNamespace, actor: Actor) { const timeline = await getHomeTimeline(domain, db, actor) @@ -31,6 +32,7 @@ WHERE objects.type = 'Note' AND outbox_objects.actor_id IN (SELECT value FROM json_each(?)) AND json_extract(objects.properties, '$.inReplyTo') IS NULL + AND outbox_objects.target = '${PUBLIC_GROUP}' ORDER by outbox_objects.published_date DESC LIMIT ? ` @@ -97,6 +99,7 @@ INNER JOIN actors ON actors.id=outbox_objects.actor_id WHERE objects.type='Note' AND ${localPreferenceQuery(localPreference)} AND json_extract(objects.properties, '$.inReplyTo') IS NULL + AND outbox_objects.target = '${PUBLIC_GROUP}' ORDER by outbox_objects.published_date DESC LIMIT ?1 OFFSET ?2 ` diff --git a/backend/test/activitypub.spec.ts b/backend/test/activitypub.spec.ts index 47fb303..79b598b 100644 --- a/backend/test/activitypub.spec.ts +++ b/backend/test/activitypub.spec.ts @@ -2,7 +2,7 @@ import { makeDB, isUrlValid } from './utils' import { MessageType } from 'wildebeest/backend/src/types/queue' import type { JWK } from 'wildebeest/backend/src/webpush/jwk' import { createPerson } from 'wildebeest/backend/src/activitypub/actors' -import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note' +import { createPublicNote, createPrivateNote } from 'wildebeest/backend/src/activitypub/objects/note' import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox' import { strict as assert } from 'node:assert/strict' import { cacheObject } from 'wildebeest/backend/src/activitypub/objects/' @@ -74,7 +74,7 @@ describe('ActivityPub', () => { await sleep(10) await addObjectInOutbox(db, actor, await createPublicNote(domain, db, 'my second status', actor)) - const res = await ap_outbox_page.handleRequest(domain, db, 'sven', userKEK) + const res = await ap_outbox_page.handleRequest(domain, db, 'sven') assert.equal(res.status, 200) const data = await res.json() @@ -83,6 +83,46 @@ describe('ActivityPub', () => { assert.equal(data.orderedItems[0].object.content, 'my second status') assert.equal(data.orderedItems[1].object.content, 'my first status') }) + + test("doesn't show private notes to anyone", 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 note = await createPrivateNote(domain, db, 'DM', actorA, actorB) + await addObjectInOutbox(db, actorA, note, undefined, actorB.id.toString()) + + { + const res = await ap_outbox_page.handleRequest(domain, db, 'a') + assert.equal(res.status, 200) + + const data = await res.json() + assert.equal(data.orderedItems.length, 0) + } + + { + const res = await ap_outbox_page.handleRequest(domain, db, 'b') + assert.equal(res.status, 200) + + const data = await res.json() + assert.equal(data.orderedItems.length, 0) + } + }) + + test("should show private notes to target but doesn't yet", async () => { + const db = await makeDB() + const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com') + const actorB = await createPerson(domain, db, userKEK, 'target@cloudflare.com') + + const note = await createPrivateNote(domain, db, 'DM', actorA, actorB) + await addObjectInOutbox(db, actorA, note) + + const res = await ap_outbox_page.handleRequest(domain, db, 'target') + assert.equal(res.status, 200) + + const data = await res.json() + assert.equal(data.orderedItems.length, 0) + }) }) describe('Objects', () => { diff --git a/backend/test/activitypub/handle.spec.ts b/backend/test/activitypub/handle.spec.ts index 238d8c3..d516cdd 100644 --- a/backend/test/activitypub/handle.spec.ts +++ b/backend/test/activitypub/handle.spec.ts @@ -14,136 +14,6 @@ const vapidKeys = {} as JWK describe('ActivityPub', () => { describe('handle Activity', () => { - test('Note to inbox stores in DB', async () => { - const db = await makeDB() - const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') - - const activity: any = { - type: 'Create', - actor: actor.id.toString(), - to: [actor.id.toString()], - cc: [], - object: { - id: 'https://example.com/note1', - type: 'Note', - content: 'test note', - }, - } - await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) - - const entry = await db - .prepare('SELECT objects.* FROM inbox_objects INNER JOIN objects ON objects.id=inbox_objects.object_id') - .first() - const properties = JSON.parse(entry.properties) - assert.equal(properties.content, 'test note') - }) - - test("Note adds in remote actor's outbox", async () => { - const remoteActorId = 'https://example.com/actor' - - globalThis.fetch = async (input: RequestInfo) => { - if (input.toString() === remoteActorId) { - return new Response( - JSON.stringify({ - id: remoteActorId, - type: 'Person', - }) - ) - } - - throw new Error('unexpected request to ' + input) - } - - const db = await makeDB() - await createPerson(domain, db, userKEK, 'sven@cloudflare.com') - - const activity: any = { - type: 'Create', - actor: remoteActorId, - to: [], - cc: [], - object: { - id: 'https://example.com/note1', - type: 'Note', - content: 'test note', - }, - } - await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) - - const entry = await db.prepare('SELECT * FROM outbox_objects WHERE actor_id=?').bind(remoteActorId).first() - assert.equal(entry.actor_id, remoteActorId) - }) - - test('local actor sends Note with mention create notification', 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 activity: any = { - type: 'Create', - actor: actorB.id.toString(), - to: [actorA.id.toString()], - cc: [], - object: { - id: 'https://example.com/note2', - type: 'Note', - content: 'test note', - }, - } - await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) - - const entry = await db.prepare('SELECT * FROM actor_notifications').first() - assert(entry) - assert.equal(entry.type, 'mention') - assert.equal(entry.actor_id.toString(), actorA.id.toString()) - assert.equal(entry.from_actor_id.toString(), actorB.id.toString()) - }) - - test('Note records reply', async () => { - const db = await makeDB() - const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') - - { - const activity: any = { - type: 'Create', - actor: actor.id.toString(), - to: [actor.id.toString()], - object: { - id: 'https://example.com/note1', - type: 'Note', - content: 'post', - }, - } - await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) - } - - { - const activity: any = { - type: 'Create', - actor: actor.id.toString(), - to: [actor.id.toString()], - object: { - inReplyTo: 'https://example.com/note1', - id: 'https://example.com/note2', - type: 'Note', - content: 'reply', - }, - } - await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) - } - - const entry = await db.prepare('SELECT * FROM actor_replies').first() - assert.equal(entry.actor_id, actor.id.toString().toString()) - - const obj: any = await getObjectById(db, entry.object_id) - assert(obj) - assert.equal(obj.originalObjectId, 'https://example.com/note2') - - const inReplyTo: any = await getObjectById(db, entry.in_reply_to_object_id) - assert(inReplyTo) - assert.equal(inReplyTo.originalObjectId, 'https://example.com/note1') - }) - describe('Announce', () => { test('records reblog in db', async () => { const db = await makeDB() @@ -314,6 +184,157 @@ describe('ActivityPub', () => { message: '`activity.object` must be of type object', }) }) + + test('Note to inbox stores in DB', async () => { + const db = await makeDB() + const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') + + const activity: any = { + type: 'Create', + actor: actor.id.toString(), + to: [actor.id.toString()], + cc: [], + object: { + id: 'https://example.com/note1', + type: 'Note', + content: 'test note', + }, + } + await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) + + const entry = await db + .prepare('SELECT objects.* FROM inbox_objects INNER JOIN objects ON objects.id=inbox_objects.object_id') + .first() + const properties = JSON.parse(entry.properties) + assert.equal(properties.content, 'test note') + }) + + test("Note adds in remote actor's outbox", async () => { + const remoteActorId = 'https://example.com/actor' + + globalThis.fetch = async (input: RequestInfo) => { + if (input.toString() === remoteActorId) { + return new Response( + JSON.stringify({ + id: remoteActorId, + type: 'Person', + }) + ) + } + + throw new Error('unexpected request to ' + input) + } + + const db = await makeDB() + await createPerson(domain, db, userKEK, 'sven@cloudflare.com') + + const activity: any = { + type: 'Create', + actor: remoteActorId, + to: [], + cc: [], + object: { + id: 'https://example.com/note1', + type: 'Note', + content: 'test note', + }, + } + await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) + + const entry = await db.prepare('SELECT * FROM outbox_objects WHERE actor_id=?').bind(remoteActorId).first() + assert.equal(entry.actor_id, remoteActorId) + }) + + test('local actor sends Note with mention create notification', 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 activity: any = { + type: 'Create', + actor: actorB.id.toString(), + to: [actorA.id.toString()], + cc: [], + object: { + id: 'https://example.com/note2', + type: 'Note', + content: 'test note', + }, + } + await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) + + const entry = await db.prepare('SELECT * FROM actor_notifications').first() + assert(entry) + assert.equal(entry.type, 'mention') + assert.equal(entry.actor_id.toString(), actorA.id.toString()) + assert.equal(entry.from_actor_id.toString(), actorB.id.toString()) + }) + + test('Note records reply', async () => { + const db = await makeDB() + const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') + + { + const activity: any = { + type: 'Create', + actor: actor.id.toString(), + to: [actor.id.toString()], + object: { + id: 'https://example.com/note1', + type: 'Note', + content: 'post', + }, + } + await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) + } + + { + const activity: any = { + type: 'Create', + actor: actor.id.toString(), + to: [actor.id.toString()], + object: { + inReplyTo: 'https://example.com/note1', + id: 'https://example.com/note2', + type: 'Note', + content: 'reply', + }, + } + await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) + } + + const entry = await db.prepare('SELECT * FROM actor_replies').first() + assert.equal(entry.actor_id, actor.id.toString().toString()) + + const obj: any = await getObjectById(db, entry.object_id) + assert(obj) + assert.equal(obj.originalObjectId, 'https://example.com/note2') + + const inReplyTo: any = await getObjectById(db, entry.in_reply_to_object_id) + assert(inReplyTo) + assert.equal(inReplyTo.originalObjectId, 'https://example.com/note1') + }) + + test('preserve Note sent with `to`', async () => { + const db = await makeDB() + const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') + + const activity: any = { + type: 'Create', + actor: actor.id.toString(), + to: ['some actor'], + cc: [], + object: { + id: 'https://example.com/note1', + type: 'Note', + content: 'test note', + }, + } + await activityHandler.handle(domain, activity, db, userKEK, adminEmail, vapidKeys) + + const row = await db.prepare('SELECT * FROM outbox_objects').first() + assert.equal(row.target, 'some actor') + }) }) describe('Update', () => { diff --git a/backend/test/mastodon/statuses.spec.ts b/backend/test/mastodon/statuses.spec.ts index 7d60461..fa6fe73 100644 --- a/backend/test/mastodon/statuses.spec.ts +++ b/backend/test/mastodon/statuses.spec.ts @@ -13,7 +13,7 @@ import { createPerson } from 'wildebeest/backend/src/activitypub/actors' import { insertLike } from 'wildebeest/backend/src/mastodon/like' import { insertReblog } from 'wildebeest/backend/src/mastodon/reblog' import { isUrlValid, makeDB, assertJSON, streamToArrayBuffer, makeQueue } from '../utils' -import * as note from 'wildebeest/backend/src/activitypub/objects/note' +import * as activities from 'wildebeest/backend/src/activitypub/activities' import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow' import { MessageType } from 'wildebeest/backend/src/types/queue' @@ -220,7 +220,7 @@ describe('Mastodon APIs', () => { assert.equal(deliveredNote.actor, `https://${domain}/ap/users/sven`) assert.equal(deliveredNote.object.attributedTo, `https://${domain}/ap/users/sven`) assert.equal(deliveredNote.object.type, 'Note') - assert(deliveredNote.object.to.includes(note.PUBLIC)) + assert(deliveredNote.object.to.includes(activities.PUBLIC_GROUP)) assert.equal(deliveredNote.object.cc.length, 1) }) diff --git a/backend/test/mastodon/timelines.spec.ts b/backend/test/mastodon/timelines.spec.ts index 76d1dd3..433c921 100644 --- a/backend/test/mastodon/timelines.spec.ts +++ b/backend/test/mastodon/timelines.spec.ts @@ -2,7 +2,7 @@ import { strict as assert } from 'node:assert/strict' import { insertReply } from 'wildebeest/backend/src/mastodon/reply' import { createImage } from 'wildebeest/backend/src/activitypub/objects/image' import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow' -import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note' +import { createPublicNote, createPrivateNote } from 'wildebeest/backend/src/activitypub/objects/note' import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox' import { createPerson } from 'wildebeest/backend/src/activitypub/actors' import { makeDB, assertCORS, assertJSON } from '../utils' @@ -53,6 +53,40 @@ describe('Mastodon APIs', () => { assert.equal(data[1].reblogs_count, 1) }) + test("home doesn't show private Notes from followed actors", async () => { + const db = await makeDB() + const actor1 = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') + const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com') + const actor3 = await createPerson(domain, db, userKEK, 'sven3@cloudflare.com') + + // actor3 follows actor1 and actor2 + await addFollowing(db, actor3, actor1, 'not needed') + await acceptFollowing(db, actor3, actor1) + await addFollowing(db, actor3, actor2, 'not needed') + await acceptFollowing(db, actor3, actor2) + + // actor2 sends a DM to actor1 + const note = await createPrivateNote(domain, db, 'DM', actor2, actor1) + await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString()) + + // actor3 shouldn't see the private note + const data = await timelines.getHomeTimeline(domain, db, actor3) + assert.equal(data.length, 0) + }) + + test("public doesn't show private Notes", async () => { + const db = await makeDB() + const actor1 = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') + const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com') + + // actor2 sends a DM to actor1 + const note = await createPrivateNote(domain, db, 'DM', actor2, actor1) + await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString()) + + const data = await timelines.getPublicTimeline(domain, db, timelines.LocalPreference.NotSet) + assert.equal(data.length, 0) + }) + test('home returns Notes from ourself', async () => { const db = await makeDB() const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com') diff --git a/functions/ap/users/[id]/outbox/page.ts b/functions/ap/users/[id]/outbox/page.ts index 543f7a9..cdd26ae 100644 --- a/functions/ap/users/[id]/outbox/page.ts +++ b/functions/ap/users/[id]/outbox/page.ts @@ -7,10 +7,11 @@ import type { ContextData } from 'wildebeest/backend/src/types/context' import type { Env } from 'wildebeest/backend/src/types/env' import type { Note } from 'wildebeest/backend/src/activitypub/objects/note' import * as activityCreate from 'wildebeest/backend/src/activitypub/activities/create' +import { PUBLIC_GROUP } from 'wildebeest/backend/src/activitypub/activities' export const onRequest: PagesFunction = async ({ request, env, params }) => { const domain = new URL(request.url).hostname - return handleRequest(domain, env.DATABASE, params.id as string, env.userKEK) + return handleRequest(domain, env.DATABASE, params.id as string) } const headers = { @@ -21,8 +22,7 @@ const headers = { const DEFAULT_LIMIT = 20 -// eslint-disable-next-line @typescript-eslint/no-unused-vars -- TODO: use userKEK -export async function handleRequest(domain: string, db: D1Database, id: string, userKEK: string): Promise { +export async function handleRequest(domain: string, db: D1Database, id: string): Promise { const handle = parseHandle(id) if (handle.domain !== null) { @@ -42,9 +42,11 @@ export async function handleRequest(domain: string, db: D1Database, id: string, SELECT objects.* FROM outbox_objects INNER JOIN objects ON objects.id = outbox_objects.object_id -WHERE outbox_objects.actor_id = ? AND objects.type = 'Note' +WHERE outbox_objects.actor_id = ?1 + AND objects.type = 'Note' + AND outbox_objects.target = '${PUBLIC_GROUP}' ORDER by outbox_objects.cdate DESC -LIMIT ? +LIMIT ?2 ` const { success, error, results } = await db.prepare(QUERY).bind(actorId.toString(), DEFAULT_LIMIT).all() diff --git a/migrations/0002_add-target-outbox_objects.sql b/migrations/0002_add-target-outbox_objects.sql new file mode 100644 index 0000000..4740970 --- /dev/null +++ b/migrations/0002_add-target-outbox_objects.sql @@ -0,0 +1,4 @@ +-- Migration number: 0002 2023-01-16T13:46:54.975Z + +ALTER TABLE outbox_objects + ADD target TEXT NOT NULL DEFAULT 'https://www.w3.org/ns/activitystreams#Public';