From b2fb4bffca7123da94157e2f472c47614873ac57 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 3 Dec 2022 23:07:33 +0100 Subject: [PATCH 1/3] server: refactor "authUser" functions into separate file They did not really fit into the DbResolver because they may fetch data from remote instances even though DbResolver is only supposed to access the database. --- .../backend/src/queue/processors/inbox.ts | 71 +++++++------------ .../src/remote/activitypub/db-resolver.ts | 50 +------------ .../src/remote/activitypub/misc/auth-user.ts | 50 +++++++++++++ 3 files changed, 77 insertions(+), 94 deletions(-) create mode 100644 packages/backend/src/remote/activitypub/misc/auth-user.ts diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 2334cc9f8..06dcf16c0 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -9,12 +9,10 @@ import { apRequestChart, federationChart, instanceChart } from '@/services/chart import { toPuny, extractDbHost } from '@/misc/convert-host.js'; import { getApId } from '@/remote/activitypub/type.js'; import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js'; -import DbResolver from '@/remote/activitypub/db-resolver.js'; -import { resolvePerson } from '@/remote/activitypub/models/person.js'; +import Resolver from '@/remote/activitypub/resolver.js'; import { LdSignature } from '@/remote/activitypub/misc/ld-signature.js'; +import { getAuthUser } from '@/remote/activitypub/misc/auth-user.js'; import { StatusError } from '@/misc/fetch.js'; -import { CacheableRemoteUser } from '@/models/entities/user.js'; -import { UserPublickey } from '@/models/entities/user-publickey.js'; import { InboxJobData } from '@/queue/types.js'; import { shouldBlockInstance } from '@/misc/skipped-instances.js'; @@ -43,75 +41,58 @@ export default async (job: Bull.Job): Promise => { return `Old keyId is no longer supported. ${keyIdLower}`; } - const dbResolver = new DbResolver(); + const resolver = new Resolver(); - // HTTP-Signature keyIdを元にDBから取得 - let authUser: { - user: CacheableRemoteUser; - key: UserPublickey | null; - } | null = await dbResolver.getAuthUserFromKeyId(signature.keyId); - - // keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得 - if (authUser == null) { - try { - authUser = await dbResolver.getAuthUserFromApId(getApId(activity.actor)); - } catch (e) { - // 対象が4xxならスキップ - if (e instanceof StatusError) { - if (e.isClientError) { - return `skip: Ignored deleted actors on both ends ${activity.actor} - ${e.statusCode}`; - } + let authUser; + try { + authUser = await getAuthUser(signature.keyId, getApId(activity.actor), resolver); + } catch (e) { + if (e instanceof StatusError) { + if (e.isClientError) { + return `skip: Ignored deleted actors on both ends ${activity.actor} - ${e.statusCode}`; + } else { throw new Error(`Error in actor ${activity.actor} - ${e.statusCode || e}`); } } } - // それでもわからなければ終了 if (authUser == null) { + // Key not found? Unacceptable! return 'skip: failed to resolve user'; + } else { + // Found key! } - // publicKey がなくても終了 - if (authUser.key == null) { - return 'skip: failed to resolve user publicKey'; - } - - // HTTP-Signatureの検証 + // verify the HTTP Signature const httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem); - // また、signatureのsignerは、activity.actorと一致する必要がある + // The signature must be valid. + // The signature must also match the actor otherwise anyone could sign any activity. if (!httpSignatureValidated || authUser.user.uri !== activity.actor) { - // 一致しなくても、でもLD-Signatureがありそうならそっちも見る + // Last resort: LD-Signature if (activity.signature) { if (activity.signature.type !== 'RsaSignature2017') { return `skip: unsupported LD-signature type ${activity.signature.type}`; } - // activity.signature.creator: https://example.oom/users/user#main-key - // みたいになっててUserを引っ張れば公開キーも入ることを期待する - if (activity.signature.creator) { - const candicate = activity.signature.creator.replace(/#.*/, ''); - await resolvePerson(candicate).catch(() => null); - } + // get user based on LD-Signature key id. + // lets assume that the creator has this common form: + // + // Then we can use it as the key id and (without fragment part) user id. + authUser = await getAuthUser(activity.signature.creator, activity.signature.creator.replace(/#.*$/, '')); - // keyIdからLD-Signatureのユーザーを取得 - authUser = await dbResolver.getAuthUserFromKeyId(activity.signature.creator); if (authUser == null) { - return 'skip: LD-Signatureのユーザーが取得できませんでした'; + return 'skip: failed to resolve LD-Signature user'; } - if (authUser.key == null) { - return 'skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした'; - } - - // LD-Signature検証 + // LD-Signature verification const ldSignature = new LdSignature(); const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false); if (!verified) { return 'skip: LD-Signatureの検証に失敗しました'; } - // もう一度actorチェック + // Again, the actor must match. if (authUser.user.uri !== activity.actor) { return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`; } diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts index 097747970..041cd929e 100644 --- a/packages/backend/src/remote/activitypub/db-resolver.ts +++ b/packages/backend/src/remote/activitypub/db-resolver.ts @@ -2,22 +2,10 @@ import escapeRegexp from 'escape-regexp'; import config from '@/config/index.js'; import { Note } from '@/models/entities/note.js'; import { CacheableRemoteUser, CacheableUser } from '@/models/entities/user.js'; -import { UserPublickey } from '@/models/entities/user-publickey.js'; import { MessagingMessage } from '@/models/entities/messaging-message.js'; -import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js'; -import { Cache } from '@/misc/cache.js'; +import { Notes, MessagingMessages } from '@/models/index.js'; import { uriPersonCache, userByIdCache } from '@/services/user-cache.js'; import { IObject, getApId } from './type.js'; -import { resolvePerson } from './models/person.js'; - -const publicKeyCache = new Cache( - Infinity, - (keyId) => UserPublickeys.findOneBy({ keyId }).then(x => x ?? undefined), -); -const publicKeyByUserIdCache = new Cache( - Infinity, - (userId) => UserPublickeys.findOneBy({ userId }).then(x => x ?? undefined), -); export type UriParseResult = { /** wether the URI was generated by us */ @@ -110,40 +98,4 @@ export default class DbResolver { return await uriPersonCache.fetch(parsed.uri) ?? null; } } - - /** - * AP KeyId => FoundKey User and Key - */ - public async getAuthUserFromKeyId(keyId: string): Promise<{ - user: CacheableRemoteUser; - key: UserPublickey; - } | null> { - const key = await publicKeyCache.fetch(keyId); - - if (key == null) return null; - - return { - user: await userByIdCache.fetch(key.userId) as CacheableRemoteUser, - key, - }; - } - - /** - * AP Actor id => FoundKey User and Key - */ - public async getAuthUserFromApId(uri: string): Promise<{ - user: CacheableRemoteUser; - key: UserPublickey | null; - } | null> { - const user = await resolvePerson(uri) as CacheableRemoteUser; - - if (user == null) return null; - - const key = await publicKeyByUserIdCache.fetch(user.id); - - return { - user, - key, - }; - } } diff --git a/packages/backend/src/remote/activitypub/misc/auth-user.ts b/packages/backend/src/remote/activitypub/misc/auth-user.ts new file mode 100644 index 000000000..172fa8809 --- /dev/null +++ b/packages/backend/src/remote/activitypub/misc/auth-user.ts @@ -0,0 +1,50 @@ +import { Cache } from '@/misc/cache.js'; +import { UserPublickeys } from '@/models/index.js'; +import { CacheableRemoteUser } from '@/models/entities/user.js'; +import { UserPublickey } from '@/models/entities/user-publickey.js'; +import { uriPersonCache, userByIdCache } from '@/services/user-cache.js'; +import { createPerson } from '@/remote/activitypub/models/person.js'; + +export type AuthUser = { + user: CacheableRemoteUser; + key: UserPublickey; +}; + +const publicKeyCache = new Cache( + Infinity, + (keyId) => UserPublickeys.findOneBy({ keyId }).then(x => x ?? undefined), +); +const publicKeyByUserIdCache = new Cache( + Infinity, + (userId) => UserPublickeys.findOneBy({ userId }).then(x => x ?? undefined), +); + +function authUserFromApId(uri: string): Promise { + return uriPersonCache.fetch(uri) + .then(async user => { + if (!user) return null; + let key = await publicKeyByUserIdCache.fetch(user.id); + if (!key) return null; + return { user, key }; + }); +} + +export async function getAuthUser(keyId: string, actorUri: string, resolver: Resolver): Promise { + let authUser = await publicKeyCache.fetch(keyId) + .then(key => { + if (!key) return null; + else return { + user: await userByIdCache.fetch(key.userId), + key, + }; + }); + if (authUser != null) return authUser; + + authUser = await authUserFromApId(actorUri); + if (authUser != null) return authUser; + + // fetch from remote and then one last try + await createPerson(actorUri, resolver); + // if this one still returns null it seems this user really does not exist + return await authUserFromApId(actorUri); +} -- 2.43.0 From 77bfe7464579b5472acbb5c754c046685cef1a8c Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 3 Dec 2022 23:45:10 +0100 Subject: [PATCH 2/3] server: refactor resolveSelf to just return the webfinger href Since the href seems to be the only attribute that is used, and I didn't want to add a full type definition this was the easier option. --- packages/backend/src/remote/resolve-user.ts | 27 ++++++++++++--------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index a9b47e77c..77dfaad7c 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -47,7 +47,7 @@ export async function resolveUser(username: string, idnHost: string | null): Pro const self = await resolveSelf(acctLower); logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); - return await createPerson(self.href); + return await createPerson(self); } // If user information is out of date, start over with webfinger @@ -60,13 +60,13 @@ export async function resolveUser(username: string, idnHost: string | null): Pro logger.info(`try resync: ${acctLower}`); const self = await resolveSelf(acctLower); - if (user.uri !== self.href) { + if (user.uri !== self) { // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. logger.info(`uri missmatch: ${acctLower}`); - logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); + logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self}`); // validate uri - const uri = new URL(self.href); + const uri = new URL(self); if (uri.hostname !== host) { throw new Error('Invalid uri'); } @@ -75,16 +75,16 @@ export async function resolveUser(username: string, idnHost: string | null): Pro usernameLower, host, }, { - uri: self.href, + uri: self, }); } else { logger.info(`uri is fine: ${acctLower}`); } - await updatePerson(self.href); + await updatePerson(self); logger.info(`return resynced remote user: ${acctLower}`); - return await Users.findOneBy({ uri: self.href }).then(u => { + return await Users.findOneBy({ uri: self }).then(u => { if (u == null) { throw new Error('user not found'); } else { @@ -97,16 +97,21 @@ export async function resolveUser(username: string, idnHost: string | null): Pro return user; } -async function resolveSelf(acctLower: string) { +/** + * Gets the Webfinger href matching rel="self". + */ +async function resolveSelf(acctLower: string): string { logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); + // get webfinger response for user const finger = await webFinger(acctLower).catch(e => { logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`); throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`); }); - const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); - if (!self) { + // try to find the rel="self" link + const self = finger.links.find(link => link.rel?.toLowerCase() === 'self'); + if (!self?.href) { logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); throw new Error('self link not found'); } - return self; + return self.href; } -- 2.43.0 From 1670d3a10ab4449437bb5c40f3a5d2eb40d8fcae Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 4 Dec 2022 00:29:45 +0100 Subject: [PATCH 3/3] activitypub: refactor to always apply recursion limit Refactor to remove as many "new Resolver" as possible. --- .../backend/src/queue/processors/inbox.ts | 2 +- .../remote/activitypub/kernel/accept/index.ts | 4 +--- .../activitypub/kernel/announce/index.ts | 4 +--- .../remote/activitypub/kernel/create/index.ts | 4 +--- .../src/remote/activitypub/kernel/index.ts | 21 +++++++++---------- .../remote/activitypub/kernel/reject/index.ts | 4 +--- .../remote/activitypub/kernel/undo/index.ts | 3 +-- .../remote/activitypub/kernel/update/index.ts | 4 +--- .../src/remote/activitypub/misc/auth-user.ts | 2 +- .../src/remote/activitypub/models/image.ts | 10 ++++----- .../src/remote/activitypub/models/mention.ts | 4 +--- .../src/remote/activitypub/models/note.ts | 6 +++--- .../src/remote/activitypub/models/person.ts | 16 +++++++------- .../src/remote/activitypub/models/question.ts | 4 ++-- .../backend/src/remote/activitypub/perform.ts | 7 ++++--- packages/backend/src/remote/resolve-user.ts | 7 ++++--- .../src/server/api/endpoints/ap/show.ts | 4 ++-- .../federation/update-remote-user.ts | 3 ++- 18 files changed, 48 insertions(+), 61 deletions(-) diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts index 06dcf16c0..bd208546e 100644 --- a/packages/backend/src/queue/processors/inbox.ts +++ b/packages/backend/src/queue/processors/inbox.ts @@ -137,6 +137,6 @@ export default async (job: Bull.Job): Promise => { }); // アクティビティを処理 - await perform(authUser.user, activity); + await perform(authUser.user, activity, resolver); return 'ok'; }; diff --git a/packages/backend/src/remote/activitypub/kernel/accept/index.ts b/packages/backend/src/remote/activitypub/kernel/accept/index.ts index ee0b6ed88..e2f5be16b 100644 --- a/packages/backend/src/remote/activitypub/kernel/accept/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/accept/index.ts @@ -4,13 +4,11 @@ import Resolver from '@/remote/activitypub/resolver.js'; import { IAccept, isFollow, getApType } from '@/remote/activitypub/type.js'; import acceptFollow from './follow.js'; -export default async (actor: CacheableRemoteUser, activity: IAccept): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAccept, resolver: Resolver): Promise => { const uri = activity.id || activity; apLogger.info(`Accept: ${uri}`); - const resolver = new Resolver(); - const object = await resolver.resolve(activity.object).catch(e => { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/kernel/announce/index.ts b/packages/backend/src/remote/activitypub/kernel/announce/index.ts index e32e8fef4..4c13d763d 100644 --- a/packages/backend/src/remote/activitypub/kernel/announce/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/announce/index.ts @@ -4,13 +4,11 @@ import Resolver from '@/remote/activitypub/resolver.js'; import { IAnnounce, getApId } from '@/remote/activitypub/type.js'; import announceNote from './note.js'; -export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise => { +export default async (actor: CacheableRemoteUser, activity: IAnnounce, resolver: Resolver): Promise => { const uri = getApId(activity); apLogger.info(`Announce: ${uri}`); - const resolver = new Resolver(); - const targetUri = getApId(activity.object); announceNote(resolver, actor, activity, targetUri); diff --git a/packages/backend/src/remote/activitypub/kernel/create/index.ts b/packages/backend/src/remote/activitypub/kernel/create/index.ts index e7531d3d3..a3abd4815 100644 --- a/packages/backend/src/remote/activitypub/kernel/create/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/create/index.ts @@ -5,7 +5,7 @@ import { ICreate, getApId, isPost, getApType } from '../../type.js'; import { apLogger } from '../../logger.js'; import createNote from './note.js'; -export default async (actor: CacheableRemoteUser, activity: ICreate): Promise => { +export default async (actor: CacheableRemoteUser, activity: ICreate, resolver: Resolver): Promise => { const uri = getApId(activity); apLogger.info(`Create: ${uri}`); @@ -26,8 +26,6 @@ export default async (actor: CacheableRemoteUser, activity: ICreate): Promise { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/kernel/index.ts b/packages/backend/src/remote/activitypub/kernel/index.ts index e2da77ef0..d1c1199b7 100644 --- a/packages/backend/src/remote/activitypub/kernel/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/index.ts @@ -18,13 +18,12 @@ import remove from './remove/index.js'; import block from './block/index.js'; import flag from './flag/index.js'; -export async function performActivity(actor: CacheableRemoteUser, activity: IObject) { +export async function performActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver) { if (isCollectionOrOrderedCollection(activity)) { - const resolver = new Resolver(); for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { const act = await resolver.resolve(item); try { - await performOneActivity(actor, act); + await performOneActivity(actor, act, resolver); } catch (err) { if (err instanceof Error || typeof err === 'string') { apLogger.error(err); @@ -32,37 +31,37 @@ export async function performActivity(actor: CacheableRemoteUser, activity: IObj } } } else { - await performOneActivity(actor, activity); + await performOneActivity(actor, activity, resolver); } } -async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise { +async function performOneActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise { if (actor.isSuspended) return; if (isCreate(activity)) { - await create(actor, activity); + await create(actor, activity, resolver); } else if (isDelete(activity)) { await performDeleteActivity(actor, activity); } else if (isUpdate(activity)) { - await performUpdateActivity(actor, activity); + await performUpdateActivity(actor, activity, resolver); } else if (isRead(activity)) { await performReadActivity(actor, activity); } else if (isFollow(activity)) { await follow(actor, activity); } else if (isAccept(activity)) { - await accept(actor, activity); + await accept(actor, activity, resolver); } else if (isReject(activity)) { - await reject(actor, activity); + await reject(actor, activity, resolver); } else if (isAdd(activity)) { await add(actor, activity).catch(err => apLogger.error(err)); } else if (isRemove(activity)) { await remove(actor, activity).catch(err => apLogger.error(err)); } else if (isAnnounce(activity)) { - await announce(actor, activity); + await announce(actor, activity, resolver); } else if (isLike(activity)) { await like(actor, activity); } else if (isUndo(activity)) { - await undo(actor, activity); + await undo(actor, activity, resolver); } else if (isBlock(activity)) { await block(actor, activity); } else if (isFlag(activity)) { diff --git a/packages/backend/src/remote/activitypub/kernel/reject/index.ts b/packages/backend/src/remote/activitypub/kernel/reject/index.ts index f75b8b44f..bb6138a6d 100644 --- a/packages/backend/src/remote/activitypub/kernel/reject/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/reject/index.ts @@ -4,13 +4,11 @@ import { IReject, isFollow, getApType } from '../../type.js'; import Resolver from '../../resolver.js'; import rejectFollow from './follow.js'; -export default async (actor: CacheableRemoteUser, activity: IReject): Promise => { +export default async (actor: CacheableRemoteUser, activity: IReject, resolver: Resolver): Promise => { const uri = activity.id || activity; apLogger.info(`Reject: ${uri}`); - const resolver = new Resolver(); - const object = await resolver.resolve(activity.object).catch(e => { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/kernel/undo/index.ts b/packages/backend/src/remote/activitypub/kernel/undo/index.ts index d85a68fdc..5233a8a5b 100644 --- a/packages/backend/src/remote/activitypub/kernel/undo/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/undo/index.ts @@ -8,7 +8,7 @@ import undoLike from './like.js'; import undoAccept from './accept.js'; import { undoAnnounce } from './announce.js'; -export default async (actor: CacheableRemoteUser, activity: IUndo): Promise => { +export default async (actor: CacheableRemoteUser, activity: IUndo, resolver: Resolver): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { throw new Error('invalid actor'); } @@ -17,7 +17,6 @@ export default async (actor: CacheableRemoteUser, activity: IUndo): Promise { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/kernel/update/index.ts b/packages/backend/src/remote/activitypub/kernel/update/index.ts index 29a352f2f..d9ca414c8 100644 --- a/packages/backend/src/remote/activitypub/kernel/update/index.ts +++ b/packages/backend/src/remote/activitypub/kernel/update/index.ts @@ -8,15 +8,13 @@ import { updatePerson } from '@/remote/activitypub/models/person.js'; /** * Updateアクティビティを捌きます */ -export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise => { +export default async (actor: CacheableRemoteUser, activity: IUpdate, resolver: Resolver): Promise => { if ('actor' in activity && actor.uri !== activity.actor) { return 'skip: invalid actor'; } apLogger.debug('Update'); - const resolver = new Resolver(); - const object = await resolver.resolve(activity.object).catch(e => { apLogger.error(`Resolution failed: ${e}`); throw e; diff --git a/packages/backend/src/remote/activitypub/misc/auth-user.ts b/packages/backend/src/remote/activitypub/misc/auth-user.ts index 172fa8809..d8a7202f2 100644 --- a/packages/backend/src/remote/activitypub/misc/auth-user.ts +++ b/packages/backend/src/remote/activitypub/misc/auth-user.ts @@ -31,7 +31,7 @@ function authUserFromApId(uri: string): Promise { export async function getAuthUser(keyId: string, actorUri: string, resolver: Resolver): Promise { let authUser = await publicKeyCache.fetch(keyId) - .then(key => { + .then(async key => { if (!key) return null; else return { user: await userByIdCache.fetch(key.userId), diff --git a/packages/backend/src/remote/activitypub/models/image.ts b/packages/backend/src/remote/activitypub/models/image.ts index d9d533f4d..d13412579 100644 --- a/packages/backend/src/remote/activitypub/models/image.ts +++ b/packages/backend/src/remote/activitypub/models/image.ts @@ -11,13 +11,13 @@ import { apLogger } from '../logger.js'; /** * Imageを作成します。 */ -export async function createImage(actor: CacheableRemoteUser, value: any): Promise { +export async function createImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { throw new Error('actor has been suspended'); } - const image = await new Resolver().resolve(value) as any; + const image = await resolver.resolve(value) as any; if (image.url == null) { throw new Error('invalid image: url not privided'); @@ -58,9 +58,9 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi * If the target Image is registered in FoundKey, return it; otherwise, fetch it from the remote server and return it. * Fetch the image from the remote server, register it in FoundKey and return it. */ -export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise { +export async function resolveImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise { // TODO - // リモートサーバーからフェッチしてきて登録 - return await createImage(actor, value); + // Fetch from remote server and register it. + return await createImage(actor, value, resolver); } diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts index 9baf82f59..38ac7dd2e 100644 --- a/packages/backend/src/remote/activitypub/models/mention.ts +++ b/packages/backend/src/remote/activitypub/models/mention.ts @@ -5,11 +5,9 @@ import { IObject, isMention, IApMention } from '../type.js'; import Resolver from '../resolver.js'; import { resolvePerson } from './person.js'; -export async function extractApMentions(tags: IObject | IObject[] | null | undefined) { +export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) { const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string)); - const resolver = new Resolver(); - const limit = promiseLimit(2); const mentionedUsers = (await Promise.all( hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))), diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index a53783785..c1aa07f05 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -63,7 +63,7 @@ export async function fetchNote(object: string | IObject): Promise /** * Noteを作成します。 */ -export async function createNote(value: string | IObject, resolver?: Resolver = new Resolver(), silent = false): Promise { +export async function createNote(value: string | IObject, resolver: Resolver, silent = false): Promise { const object: any = await resolver.resolve(value); const entryUri = getApId(value); @@ -107,7 +107,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver = let isTalk = note._misskey_talk && visibility === 'specified'; - const apMentions = await extractApMentions(note.tag); + const apMentions = await extractApMentions(note.tag, resolver); const apHashtags = await extractApHashtags(note.tag); // 添付ファイル @@ -119,7 +119,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver = note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : []; const files = note.attachment .map(attach => attach.sensitive = note.sensitive) - ? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x)) as Promise))) + ? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x, resolver)) as Promise))) .filter(image => image != null) : []; diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index a34612a74..13107f24f 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -131,7 +131,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise { +export async function createPerson(uri: string, resolver: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); if (uri.startsWith(config.url)) { @@ -238,7 +238,7 @@ export async function createPerson(uri: string, resolver?: Resolver = new Resolv ].map(img => img == null ? Promise.resolve(null) - : resolveImage(user!, img).catch(() => null), + : resolveImage(user!, img, resolver).catch(() => null), )); const avatarId = avatar ? avatar.id : null; @@ -278,7 +278,7 @@ export async function createPerson(uri: string, resolver?: Resolver = new Resolv * @param resolver Resolver * @param hint Hint of Person object (If this value is a valid Person, it is used for updating without Remote resolve.) */ -export async function updatePerson(uri: string, resolver?: Resolver = new Resolver(), hint?: IObject): Promise { +export async function updatePerson(uri: string, resolver: Resolver, hint?: IObject): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); // URIがこのサーバーを指しているならスキップ @@ -307,7 +307,7 @@ export async function updatePerson(uri: string, resolver?: Resolver = new Resolv ].map(img => img == null ? Promise.resolve(null) - : resolveImage(exist, img).catch(() => null), + : resolveImage(exist, img, resolver).catch(() => null), )); // カスタム絵文字取得 @@ -386,7 +386,7 @@ export async function updatePerson(uri: string, resolver?: Resolver = new Resolv * If the target Person is registered in FoundKey, return it; otherwise, fetch it from a remote server and return it. * Fetch the person from the remote server, register it in FoundKey, and return it. */ -export async function resolvePerson(uri: string, resolver?: Resolver): Promise { +export async function resolvePerson(uri: string, resolver: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); //#region このサーバーに既に登録されていたらそれを返す @@ -398,7 +398,7 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise { +export async function extractPollFromQuestion(source: string | IObject, resolver: Resolver): Promise { const question = await resolver.resolve(source); if (!isQuestion(question)) { @@ -39,7 +39,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver * @param resolver Resolver to use * @returns true if updated */ -export async function updateQuestion(value: string | IObject, resolver?: Resolver = new Resolver()) { +export async function updateQuestion(value: string | IObject, resolver: Resolver) { const uri = typeof value === 'string' ? value : value.id; // URIがこのサーバーを指しているならスキップ diff --git a/packages/backend/src/remote/activitypub/perform.ts b/packages/backend/src/remote/activitypub/perform.ts index c8ecff3e7..0d90ba2cf 100644 --- a/packages/backend/src/remote/activitypub/perform.ts +++ b/packages/backend/src/remote/activitypub/perform.ts @@ -3,15 +3,16 @@ import { CacheableRemoteUser } from '@/models/entities/user.js'; import { IObject } from './type.js'; import { performActivity } from './kernel/index.js'; import { updatePerson } from './models/person.js'; +import Resolver from './resolver.js'; -export default async (actor: CacheableRemoteUser, activity: IObject): Promise => { - await performActivity(actor, activity); +export default async (actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise => { + await performActivity(actor, activity, resolver); // And while I'm at it, I'll update the remote user information if it's out of date. if (actor.uri) { if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > DAY) { setImmediate(() => { - updatePerson(actor.uri!); + updatePerson(actor.uri!, resolver); }); } } diff --git a/packages/backend/src/remote/resolve-user.ts b/packages/backend/src/remote/resolve-user.ts index 77dfaad7c..309304d5e 100644 --- a/packages/backend/src/remote/resolve-user.ts +++ b/packages/backend/src/remote/resolve-user.ts @@ -9,10 +9,11 @@ import { Users } from '@/models/index.js'; import webFinger from './webfinger.js'; import { createPerson, updatePerson } from './activitypub/models/person.js'; import { remoteLogger } from './logger.js'; +import Resolver from './activitypub/resolver.js'; const logger = remoteLogger.createSubLogger('resolve-user'); -export async function resolveUser(username: string, idnHost: string | null): Promise { +export async function resolveUser(username: string, idnHost: string | null, resolver: Resolver = new Resolver()): Promise { const usernameLower = username.toLowerCase(); if (idnHost == null) { @@ -47,7 +48,7 @@ export async function resolveUser(username: string, idnHost: string | null): Pro const self = await resolveSelf(acctLower); logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`); - return await createPerson(self); + return await createPerson(self, resolver); } // If user information is out of date, start over with webfinger @@ -81,7 +82,7 @@ export async function resolveUser(username: string, idnHost: string | null): Pro logger.info(`uri is fine: ${acctLower}`); } - await updatePerson(self); + await updatePerson(self, resolver); logger.info(`return resynced remote user: ${acctLower}`); return await Users.findOneBy({ uri: self }).then(u => { diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index ef66bac3f..0fb006b70 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -114,8 +114,8 @@ async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined): return await mergePack( me, - isActor(object) ? await createPerson(getApId(object)) : null, - isPost(object) ? await createNote(getApId(object), undefined, true) : null, + isActor(object) ? await createPerson(getApId(object), resolver) : null, + isPost(object) ? await createNote(getApId(object), resolver, true) : null, ); } diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts index 8b7be8e36..88134684e 100644 --- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts +++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts @@ -1,3 +1,4 @@ +import Resolver from '@/remote/activitypub/resolver.js'; import { updatePerson } from '@/remote/activitypub/models/person.js'; import define from '../../define.js'; import { getRemoteUser } from '../../common/getters.js'; @@ -19,5 +20,5 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { const user = await getRemoteUser(ps.userId); - await updatePerson(user.uri!); + await updatePerson(user.uri!, new Resolver()); }); -- 2.43.0