diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index bb6d30d85..73085b181 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -1,5 +1,5 @@ import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { getApType, IUpdate, isActor } from '@/remote/activitypub/type.js'; +import { getApId, getApType, IUpdate, isActor } from '@/remote/activitypub/type.js'; import { apLogger } from '@/remote/activitypub/logger.js'; import { updateQuestion } from '@/remote/activitypub/models/question.js'; import { Resolver } from '@/remote/activitypub/resolver.js'; @@ -21,7 +21,11 @@ export default async (actor: CacheableRemoteUser, activity: IUpdate, resolver: R }); if (isActor(object)) { - await updatePerson(actor.uri!, resolver, object); + if (actor.uri !== getApId(object)) { + return 'skip: actor id !== updated actor id'; + } + + await updatePerson(object, resolver); return 'ok: Person updated'; } else if (getApType(object) === 'Question') { await updateQuestion(object, resolver).catch(e => console.log(e)); diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index ffb8c28d2..e52d86d79 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -28,9 +28,7 @@ import { extractApHashtags } from './tag.js'; import { extractPollFromQuestion } from './question.js'; import { extractApMentions } from './mention.js'; -export function validateNote(object: any, uri: string): Error | null { - const expectHost = extractDbHost(uri); - +export function validateNote(object: IObject): Error | null { if (object == null) { return new Error('invalid Note: object is null'); } @@ -39,12 +37,20 @@ export function validateNote(object: any, uri: string): Error | null { return new Error(`invalid Note: invalid object type ${getApType(object)}`); } - if (object.id && extractDbHost(object.id) !== expectHost) { - return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${extractDbHost(object.id)}`); + const id = getApId(object); + if (id == null) { + // Only transient objects or anonymous objects may not have an id or an id that is explicitly null. + // We consider all Notes as not transient and not anonymous so require ids for them. + return new Error(`invalid Note: id required but not present`); } - if (object.attributedTo && extractDbHost(getOneApId(object.attributedTo)) !== expectHost) { - return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractDbHost(object.attributedTo)}`); + // Check that the server is authorized to act on behalf of this author. + const expectHost = extractDbHost(id); + const attributedToHost = object.attributedTo + ? extractDbHost(getOneApId(object.attributedTo)) + : null; + if (attributedToHost !== expectHost) { + return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${attributedToHost}`); } return null; @@ -64,10 +70,9 @@ export async function fetchNote(object: string | IObject): Promise * Noteを作成します。 */ export async function createNote(value: string | IObject, resolver: Resolver, silent = false): Promise { - const object: any = await resolver.resolve(value); + const object: IObject = await resolver.resolve(value); - const entryUri = getApId(value); - const err = validateNote(object, entryUri); + const err = validateNote(object); if (err) { apLogger.error(`${err.message}`, { resolver: { diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index e6a6779c4..669ad366d 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -39,8 +39,7 @@ const summaryLength = 2048; * @param x Fetched object * @param uri Fetch target URI */ -function validateActor(x: IObject, uri: string): IActor { - const expectHost = toPuny(new URL(uri).hostname); +function validateActor(x: IObject): IActor { if (x == null) { throw new Error('invalid Actor: object is null'); @@ -50,7 +49,10 @@ function validateActor(x: IObject, uri: string): IActor { throw new Error(`invalid Actor type '${x.type}'`); } - if (!(typeof x.id === 'string' && x.id.length > 0)) { + const uri = getApId(x); + if (uri == null) { + // Only transient objects or anonymous objects may not have an id or an id that is explicitly null. + // We consider all actors as not transient and not anonymous so require ids for them. throw new Error('invalid Actor: wrong id'); } @@ -78,16 +80,12 @@ function validateActor(x: IObject, uri: string): IActor { x.summary = truncate(x.summary, summaryLength); } - const idHost = toPuny(new URL(x.id!).hostname); - if (idHost !== expectHost) { - throw new Error('invalid Actor: id has different host'); - } - if (x.publicKey) { if (typeof x.publicKey.id !== 'string') { throw new Error('invalid Actor: publicKey.id is not a string'); } + const expectHost = extractDbHost(uri); const publicKeyIdHost = toPuny(new URL(x.publicKey.id).hostname); if (publicKeyIdHost !== expectHost) { throw new Error('invalid Actor: publicKey.id has different host'); @@ -140,7 +138,7 @@ export async function createPerson(uri: string, resolver: Resolver): Promise { - if (typeof uri !== 'string') throw new Error('uri is not string'); +export async function updatePerson(value: IObject | string, resolver: Resolver): Promise { + const uri = getApId(value); // URIがこのサーバーを指しているならスキップ if (uri.startsWith(config.url + '/')) { @@ -294,9 +292,9 @@ export async function updatePerson(uri: string, resolver: Resolver, hint?: IObje } //#endregion - const object = hint || await resolver.resolve(uri); + const object = await resolver.resolve(value); - const person = validateActor(object, uri); + const person = validateActor(object); apLogger.info(`Updating the Person: ${person.id}`);