kopia lustrzana https://github.com/cloudflare/wildebeest
support direct visiblity and reject others
Refs https://github.com/cloudflare/wildebeest/issues/303pull/336/head
rodzic
a876ef9ad2
commit
c09edecc34
|
@ -52,12 +52,12 @@ export async function createPublicNote(
|
||||||
return (await objects.createObject(domain, db, NOTE, properties, actorId, true)) as Note
|
return (await objects.createObject(domain, db, NOTE, properties, actorId, true)) as Note
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPrivateNote(
|
export async function createDirectNote(
|
||||||
domain: string,
|
domain: string,
|
||||||
db: Database,
|
db: Database,
|
||||||
content: string,
|
content: string,
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
targetActor: Actor,
|
targetActors: Array<Actor>,
|
||||||
attachment: Array<objects.APObject> = [],
|
attachment: Array<objects.APObject> = [],
|
||||||
extraProperties: any = {}
|
extraProperties: any = {}
|
||||||
): Promise<Note> {
|
): Promise<Note> {
|
||||||
|
@ -66,7 +66,7 @@ export async function createPrivateNote(
|
||||||
const properties = {
|
const properties = {
|
||||||
attributedTo: actorId,
|
attributedTo: actorId,
|
||||||
content,
|
content,
|
||||||
to: [targetActor.id.toString()],
|
to: targetActors.map((a) => a.id.toString()),
|
||||||
cc: [],
|
cc: [],
|
||||||
|
|
||||||
// FIXME: stub values
|
// FIXME: stub values
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { MessageType } from 'wildebeest/backend/src/types/queue'
|
||||||
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
|
import type { JWK } from 'wildebeest/backend/src/webpush/jwk'
|
||||||
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
import * as actors from 'wildebeest/backend/src/activitypub/actors'
|
import * as actors from 'wildebeest/backend/src/activitypub/actors'
|
||||||
import { createPrivateNote, createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
import { createDirectNote, createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||||
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
||||||
import { strict as assert } from 'node:assert/strict'
|
import { strict as assert } from 'node:assert/strict'
|
||||||
import { cacheObject } from 'wildebeest/backend/src/activitypub/objects/'
|
import { cacheObject } from 'wildebeest/backend/src/activitypub/objects/'
|
||||||
|
@ -146,7 +146,7 @@ describe('ActivityPub', () => {
|
||||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||||
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
|
const actorB = await createPerson(domain, db, userKEK, 'b@cloudflare.com')
|
||||||
|
|
||||||
const note = await createPrivateNote(domain, db, 'DM', actorA, actorB)
|
const note = await createDirectNote(domain, db, 'DM', actorA, [actorB])
|
||||||
await addObjectInOutbox(db, actorA, note, undefined, actorB.id.toString())
|
await addObjectInOutbox(db, actorA, note, undefined, actorB.id.toString())
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -171,7 +171,7 @@ describe('ActivityPub', () => {
|
||||||
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
const actorA = await createPerson(domain, db, userKEK, 'a@cloudflare.com')
|
||||||
const actorB = await createPerson(domain, db, userKEK, 'target@cloudflare.com')
|
const actorB = await createPerson(domain, db, userKEK, 'target@cloudflare.com')
|
||||||
|
|
||||||
const note = await createPrivateNote(domain, db, 'DM', actorA, actorB)
|
const note = await createDirectNote(domain, db, 'DM', actorA, [actorB])
|
||||||
await addObjectInOutbox(db, actorA, note)
|
await addObjectInOutbox(db, actorA, note)
|
||||||
|
|
||||||
const res = await ap_outbox_page.handleRequest(domain, db, 'target')
|
const res = await ap_outbox_page.handleRequest(domain, db, 'target')
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { MessageType } from 'wildebeest/backend/src/types/queue'
|
||||||
import { MastodonStatus } from 'wildebeest/backend/src/types'
|
import { MastodonStatus } from 'wildebeest/backend/src/types'
|
||||||
import { mastodonIdSymbol, getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
|
import { mastodonIdSymbol, getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
|
||||||
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
||||||
|
import * as timelines from 'wildebeest/backend/src/mastodon/timeline'
|
||||||
|
|
||||||
const userKEK = 'test_kek4'
|
const userKEK = 'test_kek4'
|
||||||
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
||||||
|
@ -1036,5 +1037,130 @@ describe('Mastodon APIs', () => {
|
||||||
assert.equal(res.status, 422)
|
assert.equal(res.status, 422)
|
||||||
assertJSON(res)
|
assertJSON(res)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('create status with direct visibility', async () => {
|
||||||
|
const db = await makeDB()
|
||||||
|
const queue = makeQueue()
|
||||||
|
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||||
|
const actor1 = await createPerson(domain, db, userKEK, 'actor1@cloudflare.com')
|
||||||
|
const actor2 = await createPerson(domain, db, userKEK, 'actor2@cloudflare.com')
|
||||||
|
|
||||||
|
let deliveredActivity1: any = null
|
||||||
|
let deliveredActivity2: any = null
|
||||||
|
|
||||||
|
globalThis.fetch = async (input: RequestInfo | Request) => {
|
||||||
|
if (
|
||||||
|
input.toString() === 'https://cloudflare.com/.well-known/webfinger?resource=acct%3Aactor1%40cloudflare.com'
|
||||||
|
) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
rel: 'self',
|
||||||
|
type: 'application/activity+json',
|
||||||
|
href: actor1.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
input.toString() === 'https://cloudflare.com/.well-known/webfinger?resource=acct%3Aactor2%40cloudflare.com'
|
||||||
|
) {
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
rel: 'self',
|
||||||
|
type: 'application/activity+json',
|
||||||
|
href: actor2.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (input.url === actor1.inbox.toString()) {
|
||||||
|
deliveredActivity1 = await (input as Request).json()
|
||||||
|
return new Response()
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
if (input.url === actor2.inbox.toString()) {
|
||||||
|
deliveredActivity2 = await (input as Request).json()
|
||||||
|
return new Response()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('unexpected request to ' + input)
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
status: '@actor1 @actor2 hey',
|
||||||
|
visibility: 'direct',
|
||||||
|
}
|
||||||
|
const req = new Request('https://' + domain, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
|
||||||
|
assert.equal(res.status, 200)
|
||||||
|
|
||||||
|
assert(deliveredActivity1)
|
||||||
|
assert(deliveredActivity2)
|
||||||
|
delete deliveredActivity1.id
|
||||||
|
delete deliveredActivity2.id
|
||||||
|
|
||||||
|
assert.deepEqual(deliveredActivity1, deliveredActivity2)
|
||||||
|
assert.equal(deliveredActivity1.to.length, 2)
|
||||||
|
assert.equal(deliveredActivity1.to[0], actor1.id.toString())
|
||||||
|
assert.equal(deliveredActivity1.to[1], actor2.id.toString())
|
||||||
|
assert.equal(deliveredActivity1.cc.length, 0)
|
||||||
|
|
||||||
|
// ensure that the private note doesn't show up in public timeline
|
||||||
|
const timeline = await timelines.getPublicTimeline(domain, db, timelines.LocalPreference.NotSet)
|
||||||
|
assert.equal(timeline.length, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('create status with unlisted visibility', async () => {
|
||||||
|
const db = await makeDB()
|
||||||
|
const queue = makeQueue()
|
||||||
|
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
status: 'something nice',
|
||||||
|
visibility: 'unlisted',
|
||||||
|
}
|
||||||
|
const req = new Request('https://' + domain, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
|
||||||
|
assert.equal(res.status, 422)
|
||||||
|
assertJSON(res)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('create status with private visibility', async () => {
|
||||||
|
const db = await makeDB()
|
||||||
|
const queue = makeQueue()
|
||||||
|
const actor = await createPerson(domain, db, userKEK, 'sven@cloudflare.com')
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
status: 'something nice',
|
||||||
|
visibility: 'private',
|
||||||
|
}
|
||||||
|
const req = new Request('https://' + domain, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
})
|
||||||
|
|
||||||
|
const res = await statuses.handleRequest(req, db, actor, userKEK, queue, cache)
|
||||||
|
assert.equal(res.status, 422)
|
||||||
|
assertJSON(res)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { strict as assert } from 'node:assert/strict'
|
||||||
import { createReply } from 'wildebeest/backend/test/shared.utils'
|
import { createReply } from 'wildebeest/backend/test/shared.utils'
|
||||||
import { createImage } from 'wildebeest/backend/src/activitypub/objects/image'
|
import { createImage } from 'wildebeest/backend/src/activitypub/objects/image'
|
||||||
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
import { addFollowing, acceptFollowing } from 'wildebeest/backend/src/mastodon/follow'
|
||||||
import { createPublicNote, createPrivateNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
import { createPublicNote, createDirectNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||||
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
||||||
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
import { createPerson } from 'wildebeest/backend/src/activitypub/actors'
|
||||||
import { makeDB, assertCORS, assertJSON, makeCache } from '../utils'
|
import { makeDB, assertCORS, assertJSON, makeCache } from '../utils'
|
||||||
|
@ -67,7 +67,7 @@ describe('Mastodon APIs', () => {
|
||||||
await acceptFollowing(db, actor3, actor2)
|
await acceptFollowing(db, actor3, actor2)
|
||||||
|
|
||||||
// actor2 sends a DM to actor1
|
// actor2 sends a DM to actor1
|
||||||
const note = await createPrivateNote(domain, db, 'DM', actor2, actor1)
|
const note = await createDirectNote(domain, db, 'DM', actor2, [actor1])
|
||||||
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
|
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
|
||||||
|
|
||||||
// actor3 shouldn't see the private note
|
// actor3 shouldn't see the private note
|
||||||
|
@ -100,7 +100,7 @@ describe('Mastodon APIs', () => {
|
||||||
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
|
const actor2 = await createPerson(domain, db, userKEK, 'sven2@cloudflare.com')
|
||||||
|
|
||||||
// actor2 sends a DM to actor1
|
// actor2 sends a DM to actor1
|
||||||
const note = await createPrivateNote(domain, db, 'DM', actor2, actor1)
|
const note = await createDirectNote(domain, db, 'DM', actor2, [actor1])
|
||||||
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
|
await addObjectInOutbox(db, actor2, note, undefined, actor1.id.toString())
|
||||||
|
|
||||||
const data = await timelines.getPublicTimeline(domain, db, timelines.LocalPreference.NotSet)
|
const data = await timelines.getPublicTimeline(domain, db, timelines.LocalPreference.NotSet)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import * as timeline from 'wildebeest/backend/src/mastodon/timeline'
|
||||||
import type { Queue, DeliverMessageBody } from 'wildebeest/backend/src/types/queue'
|
import type { Queue, DeliverMessageBody } from 'wildebeest/backend/src/types/queue'
|
||||||
import type { Document } from 'wildebeest/backend/src/activitypub/objects'
|
import type { Document } from 'wildebeest/backend/src/activitypub/objects'
|
||||||
import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
|
import { getObjectByMastodonId } from 'wildebeest/backend/src/activitypub/objects'
|
||||||
import { createStatus, getMentions } from 'wildebeest/backend/src/mastodon/status'
|
import { getMentions } from 'wildebeest/backend/src/mastodon/status'
|
||||||
import { getHashtags, insertHashtags } from 'wildebeest/backend/src/mastodon/hashtag'
|
import { getHashtags, insertHashtags } from 'wildebeest/backend/src/mastodon/hashtag'
|
||||||
import * as activities from 'wildebeest/backend/src/activitypub/activities/create'
|
import * as activities from 'wildebeest/backend/src/activitypub/activities/create'
|
||||||
import type { Env } from 'wildebeest/backend/src/types/env'
|
import type { Env } from 'wildebeest/backend/src/types/env'
|
||||||
|
@ -27,6 +27,8 @@ import * as idempotency from 'wildebeest/backend/src/mastodon/idempotency'
|
||||||
import { newMention } from 'wildebeest/backend/src/activitypub/objects/mention'
|
import { newMention } from 'wildebeest/backend/src/activitypub/objects/mention'
|
||||||
import { originalObjectIdSymbol } from 'wildebeest/backend/src/activitypub/objects'
|
import { originalObjectIdSymbol } from 'wildebeest/backend/src/activitypub/objects'
|
||||||
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
|
import { type Database, getDatabase } from 'wildebeest/backend/src/database'
|
||||||
|
import { createPublicNote, createDirectNote } from 'wildebeest/backend/src/activitypub/objects/note'
|
||||||
|
import { addObjectInOutbox } from 'wildebeest/backend/src/activitypub/actors/outbox'
|
||||||
|
|
||||||
type StatusCreate = {
|
type StatusCreate = {
|
||||||
status: string
|
status: string
|
||||||
|
@ -118,7 +120,16 @@ export async function handleRequest(
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = enrichStatus(body.status, mentions)
|
const content = enrichStatus(body.status, mentions)
|
||||||
const note = await createStatus(domain, db, connectedActor, content, mediaAttachments, extraProperties)
|
|
||||||
|
let note
|
||||||
|
|
||||||
|
if (body.visibility === 'public') {
|
||||||
|
note = await createPublicNote(domain, db, content, connectedActor, mediaAttachments, extraProperties)
|
||||||
|
} else if (body.visibility === 'direct') {
|
||||||
|
note = await createDirectNote(domain, db, content, connectedActor, mentions, mediaAttachments, extraProperties)
|
||||||
|
} else {
|
||||||
|
return errors.validationError(`status with visibility: ${body.visibility}`)
|
||||||
|
}
|
||||||
|
|
||||||
if (hashtags.length > 0) {
|
if (hashtags.length > 0) {
|
||||||
await insertHashtags(db, note, hashtags)
|
await insertHashtags(db, note, hashtags)
|
||||||
|
@ -132,11 +143,27 @@ export async function handleRequest(
|
||||||
const activity = activities.create(domain, connectedActor, note)
|
const activity = activities.create(domain, connectedActor, note)
|
||||||
await deliverFollowers(db, userKEK, connectedActor, activity, queue)
|
await deliverFollowers(db, userKEK, connectedActor, activity, queue)
|
||||||
|
|
||||||
|
if (body.visibility === 'public') {
|
||||||
|
await addObjectInOutbox(db, connectedActor, note)
|
||||||
|
|
||||||
|
// A public note is sent to the public group URL and cc'ed any mentioned
|
||||||
|
// actors.
|
||||||
|
for (let i = 0, len = mentions.length; i < len; i++) {
|
||||||
|
const targetActor = mentions[i]
|
||||||
|
note.cc.push(targetActor.id.toString())
|
||||||
|
}
|
||||||
|
} else if (body.visibility === 'direct') {
|
||||||
|
// A direct note is sent to mentioned people only
|
||||||
|
for (let i = 0, len = mentions.length; i < len; i++) {
|
||||||
|
const targetActor = mentions[i]
|
||||||
|
await addObjectInOutbox(db, connectedActor, note, undefined, targetActor.id.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// If the status is mentioning other persons, we need to delivery it to them.
|
// If the status is mentioning other persons, we need to delivery it to them.
|
||||||
for (let i = 0, len = mentions.length; i < len; i++) {
|
for (let i = 0, len = mentions.length; i < len; i++) {
|
||||||
const targetActor = mentions[i]
|
const targetActor = mentions[i]
|
||||||
note.cc.push(targetActor.id.toString())
|
|
||||||
const activity = activities.create(domain, connectedActor, note)
|
const activity = activities.create(domain, connectedActor, note)
|
||||||
const signingKey = await getSigningKey(userKEK, db, connectedActor)
|
const signingKey = await getSigningKey(userKEK, db, connectedActor)
|
||||||
await deliverToActor(signingKey, connectedActor, targetActor, activity, domain)
|
await deliverToActor(signingKey, connectedActor, targetActor, activity, domain)
|
||||||
|
|
Ładowanie…
Reference in New Issue