From 54fe8ca600bbecb15236a5ccddef5f5f7a9e2fb8 Mon Sep 17 00:00:00 2001 From: tamaina Date: Fri, 1 Mar 2024 06:49:38 +0000 Subject: [PATCH] fetchPersonWithRenewal --- .../src/core/FetchInstanceMetadataService.ts | 23 ++++++++++++------- .../core/activitypub/ApDbResolverService.ts | 2 +- .../src/core/activitypub/ApInboxService.ts | 10 -------- .../src/core/activitypub/ApRequestService.ts | 23 ++++++++++++++++--- .../activitypub/models/ApPersonService.ts | 20 ++++++++++++++-- .../queue/processors/InboxProcessorService.ts | 12 ++++++++-- 6 files changed, 64 insertions(+), 26 deletions(-) diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index 63168afd7d..21e84a7454 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -40,6 +40,7 @@ type NodeInfo = { @Injectable() export class FetchInstanceMetadataService { private logger: Logger; + private httpColon = 'https://'; constructor( private httpRequestService: HttpRequestService, @@ -49,6 +50,7 @@ export class FetchInstanceMetadataService { private redisClient: Redis.Redis, ) { this.logger = this.loggerService.getLogger('metadata', 'cyan'); + this.httpColon = 'http://'; } @bindThis @@ -72,8 +74,7 @@ export class FetchInstanceMetadataService { const _instance = await this.federatedInstanceService.fetch(host); const now = Date.now(); if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 3)) { - // unlock at the finally caluse - return; + throw new Error('Skip because updated recently'); } } @@ -119,6 +120,12 @@ export class FetchInstanceMetadataService { await this.federatedInstanceService.update(instance.id, updates); this.logger.succ(`Successfuly updated metadata of ${instance.host}`); + this.logger.debug('Updated metadata:', { + info: !!info, + dom: !!dom, + manifest: !!manifest, + updates, + }); } catch (e) { this.logger.error(`Failed to update metadata of ${instance.host}: ${e}`); } finally { @@ -131,7 +138,7 @@ export class FetchInstanceMetadataService { this.logger.info(`Fetching nodeinfo of ${instance.host} ...`); try { - const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo') + const wellknown = await this.httpRequestService.getJson(this.httpColon + instance.host + '/.well-known/nodeinfo') .catch(err => { if (err.statusCode === 404) { throw new Error('No nodeinfo provided'); @@ -174,7 +181,7 @@ export class FetchInstanceMetadataService { private async fetchDom(instance: MiInstance): Promise { this.logger.info(`Fetching HTML of ${instance.host} ...`); - const url = 'https://' + instance.host; + const url = this.httpColon + instance.host; const html = await this.httpRequestService.getHtml(url); @@ -186,7 +193,7 @@ export class FetchInstanceMetadataService { @bindThis private async fetchManifest(instance: MiInstance): Promise | null> { - const url = 'https://' + instance.host; + const url = this.httpColon + instance.host; const manifestUrl = url + '/manifest.json'; @@ -197,7 +204,7 @@ export class FetchInstanceMetadataService { @bindThis private async fetchFaviconUrl(instance: MiInstance, doc: DOMWindow['document'] | null): Promise { - const url = 'https://' + instance.host; + const url = this.httpColon + instance.host; if (doc) { // https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043 @@ -224,12 +231,12 @@ export class FetchInstanceMetadataService { @bindThis private async fetchIconUrl(instance: MiInstance, doc: DOMWindow['document'] | null, manifest: Record | null): Promise { if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) { - const url = 'https://' + instance.host; + const url = this.httpColon + instance.host; return (new URL(manifest.icons[0].src, url)).href; } if (doc) { - const url = 'https://' + instance.host; + const url = this.httpColon + instance.host; // https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043 const links = Array.from(doc.getElementsByTagName('link')).reverse(); diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 4013eaddc0..d95bd3bbda 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -122,7 +122,7 @@ export class ApDbResolverService implements OnApplicationShutdown { user: MiRemoteUser; key: MiUserPublickey | null; } | null> { - const user = await this.apPersonService.resolvePerson(uri) as MiRemoteUser; + const user = await this.apPersonService.resolvePerson(uri, undefined, true) as MiRemoteUser; if (user.isDeleted) return null; const keys = await this.publicKeyByUserIdCache.fetch( diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index b0f56a5d82..13b86c2920 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -36,7 +36,6 @@ import { ApResolverService } from './ApResolverService.js'; import { ApAudienceService } from './ApAudienceService.js'; import { ApPersonService } from './models/ApPersonService.js'; import { ApQuestionService } from './models/ApQuestionService.js'; -import { CacheService } from '@/core/CacheService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import type { Resolver } from './ApResolverService.js'; import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js'; @@ -109,15 +108,6 @@ export class ApInboxService { } else { await this.performOneActivity(actor, activity); } - - // ついでにリモートユーザーの情報が古かったら更新しておく - if (actor.uri) { - if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - setImmediate(() => { - this.apPersonService.updatePerson(actor.uri); - }); - } - } } @bindThis diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index cc2f05aba2..36a4b34de1 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -107,16 +107,24 @@ export class ApRequestService { @bindThis public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown, level: string): Promise { const body = typeof object === 'string' ? object : JSON.stringify(object); - + const key = await this.getPrivateKey(user.id, level); const req = createSignedPost({ level, - key: await this.getPrivateKey(user.id, level), + key, url, body, additionalHeaders: { + 'User-Agent': this.config.userAgent, }, }); + this.logger.debug('create signed post', { + version: 'draft', + level, + url, + keyId: key.keyId, + }); + await this.httpRequestService.send(url, { method: req.request.method, headers: req.request.headers, @@ -131,14 +139,23 @@ export class ApRequestService { */ @bindThis public async signedGet(url: string, user: { id: MiUser['id'] }, level: string): Promise { + const key = await this.getPrivateKey(user.id, level); const req = createSignedGet({ level, - key: await this.getPrivateKey(user.id, level), + key, url, additionalHeaders: { + 'User-Agent': this.config.userAgent, }, }); + this.logger.debug('create signed get', { + version: 'draft', + level, + url, + keyId: key.keyId, + }); + const res = await this.httpRequestService.send(url, { method: req.request.method, headers: req.request.headers, diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index 5100a24600..da4a42f73e 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -248,6 +248,22 @@ export class ApPersonService implements OnModuleInit { return null; } + @bindThis + async fetchPersonWithRenewal(uri: string): Promise { + const exist = await this.fetchPerson(uri); + if (exist == null) return null; + + // ついでにリモートユーザーの情報が古かったら更新しておく + if (this.userEntityService.isRemoteUser(exist)) { + if (exist.lastFetchedAt == null || Date.now() - exist.lastFetchedAt.getTime() > 1000 * 60 * 60 * 3) { + await this.updatePerson(exist.uri); + return await this.fetchPerson(uri); + } + } + + return exist; + } + private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any): Promise>> { if (user == null) throw new Error('failed to create user: user is null'); @@ -624,9 +640,9 @@ export class ApPersonService implements OnModuleInit { * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ @bindThis - public async resolvePerson(uri: string, resolver?: Resolver): Promise { + public async resolvePerson(uri: string, resolver?: Resolver, withRenewal = false): Promise { //#region このサーバーに既に登録されていたらそれを返す - const exist = await this.fetchPerson(uri); + const exist = withRenewal ? await this.fetchPersonWithRenewal(uri) : await this.fetchPerson(uri); if (exist) return exist; //#endregion diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 85ce6417b8..4d7f76c0d9 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -19,6 +19,7 @@ import type { MiRemoteUser } from '@/models/User.js'; import type { MiUserPublickey } from '@/models/UserPublickey.js'; import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js'; import { StatusError } from '@/misc/status-error.js'; +import * as Acct from '@/misc/acct.js'; import { UtilityService } from '@/core/UtilityService.js'; import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js'; import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js'; @@ -79,7 +80,6 @@ export class InboxProcessorService { key: MiUserPublickey | null; } | null = null; - // keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得 try { authUser = await this.apDbResolverService.getAuthUserFromApId(getApId(activity.actor), signature.keyId); } catch (err) { @@ -103,7 +103,15 @@ export class InboxProcessorService { } // HTTP-Signatureの検証 - const httpSignatureValidated = verifyDraftSignature(signature, authUser.key.keyPem); + const errorLogger = (ms: any) => this.logger.error(ms); + const httpSignatureValidated = verifyDraftSignature(signature, authUser.key.keyPem, errorLogger); + this.logger.debug('Inbox message validation: ', { + userId: authUser.user.id, + userAcct: Acct.toString(authUser.user), + parsedKeyId: signature.keyId, + foundKeyId: authUser.key.keyId, + httpSignatureValidated, + }); // また、signatureのsignerは、activity.actorと一致する必要がある if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {