From 4a1693776f875355f2c7495f0ec3857a49de5bdf Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 14 Sep 2025 09:01:07 -0400 Subject: [PATCH 1/7] when processing an activity, refetch the user's public key if they don't already have one --- .../backend/src/queue/processors/InboxProcessorService.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 4efbb088c6..13b2885263 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -164,7 +164,12 @@ export class InboxProcessorService implements OnApplicationShutdown { // publicKey がなくても終了 if (authUser.key == null) { - throw new Bull.UnrecoverableError(`skip: failed to resolve user publicKey ${actorId}`); + // See if a key has become available since we fetched the actor + authUser.key = await this.apDbResolverService.refetchPublicKeyForApId(authUser.user); + if (authUser.key == null) { + // If it's still missing, then give up + throw new Bull.UnrecoverableError(`skip: failed to resolve user publicKey ${actorId}`); + } } // HTTP-Signatureの検証 From 48c296c5ce317deb670579b9dcb147732fa6f930 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 14 Sep 2025 09:01:39 -0400 Subject: [PATCH 2/7] don't refetch a user's public key if we've already updated them recently --- .../src/core/activitypub/ApDbResolverService.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 87e3b7ed5f..30eb45433e 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -15,6 +15,7 @@ import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import type { MiLocalUser, MiRemoteUser } from '@/models/User.js'; import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js'; +import { IdService } from '@/core/IdService.js'; import { getApId } from './type.js'; import { ApPersonService } from './models/ApPersonService.js'; import type { IObject } from './type.js'; @@ -40,6 +41,7 @@ export class ApDbResolverService implements OnApplicationShutdown { private apPersonService: ApPersonService, private apLoggerService: ApLoggerService, private utilityService: UtilityService, + private readonly idService: IdService, ) { // Caches moved to ApPersonService to avoid circular dependency } @@ -125,8 +127,19 @@ export class ApDbResolverService implements OnApplicationShutdown { */ @bindThis public async refetchPublicKeyForApId(user: MiRemoteUser): Promise { - this.apLoggerService.logger.debug(`Updating public key for user ${user.id} (${user.uri})`); + // Don't re-fetch if we've updated the user recently + const maxUpdatedTime = Date.now() - 60_000; // 1 minute ago + if ( + (user.lastFetchedAt && user.lastFetchedAt.valueOf() > maxUpdatedTime) || + (user.updatedAt && user.updatedAt.valueOf() > maxUpdatedTime) || + this.idService.parse(user.id).date.valueOf() > maxUpdatedTime + ) { + this.apLoggerService.logger.debug(`Not updating public key for user ${user.id} (${user.uri}): already checked recently`); + } else { + this.apLoggerService.logger.debug(`Updating public key for user ${user.id} (${user.uri})`); + } + // findPublicKeyByUserId pulls from a cache, but updatePerson also updates that cache if there's any changes. const oldKey = await this.apPersonService.findPublicKeyByUserId(user.id); await this.apPersonService.updatePerson(user.uri); const newKey = await this.apPersonService.findPublicKeyByUserId(user.id); From 0cc839da8a6a10d6d9856a032a84d5b2f7000ca0 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sun, 14 Sep 2025 09:01:57 -0400 Subject: [PATCH 3/7] improve logging for edge cases in refetchPublicKeyForApId --- .../src/core/activitypub/ApDbResolverService.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 30eb45433e..4fdc331e7e 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -144,14 +144,18 @@ export class ApDbResolverService implements OnApplicationShutdown { await this.apPersonService.updatePerson(user.uri); const newKey = await this.apPersonService.findPublicKeyByUserId(user.id); - if (newKey) { - if (oldKey && newKey.keyPem === oldKey.keyPem) { + if (newKey && oldKey) { + if (newKey.keyPem === oldKey.keyPem) { this.apLoggerService.logger.debug(`Public key is up-to-date for user ${user.id} (${user.uri})`); } else { this.apLoggerService.logger.info(`Updated public key for user ${user.id} (${user.uri})`); } + } else if (newKey) { + this.apLoggerService.logger.info(`Registered public key for user ${user.id} (${user.uri})`); + } else if (oldKey) { + this.apLoggerService.logger.info(`Deleted public key for user ${user.id} (${user.uri})`); } else { - this.apLoggerService.logger.warn(`Failed to update public key for user ${user.id} (${user.uri})`); + this.apLoggerService.logger.warn(`Could not find any public key for user ${user.id} (${user.uri})`); } return newKey ?? oldKey; From 3216984ef6c7ec560e798c10003a8132e2bf379c Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 6 Oct 2025 00:25:42 -0400 Subject: [PATCH 4/7] use TimeService in ApDbResolverService.ts --- packages/backend/src/core/activitypub/ApDbResolverService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 4fdc331e7e..cae8f6ad44 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -10,6 +10,7 @@ import type { Config } from '@/config.js'; import { MemoryKVCache } from '@/misc/cache.js'; import type { MiUserPublickey } from '@/models/UserPublickey.js'; import { CacheService } from '@/core/CacheService.js'; +import { TimeService } from '@/core/TimeService.js'; import { UtilityService } from '@/core/UtilityService.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; @@ -42,6 +43,7 @@ export class ApDbResolverService implements OnApplicationShutdown { private apLoggerService: ApLoggerService, private utilityService: UtilityService, private readonly idService: IdService, + private readonly timeService: TimeService, ) { // Caches moved to ApPersonService to avoid circular dependency } @@ -128,7 +130,7 @@ export class ApDbResolverService implements OnApplicationShutdown { @bindThis public async refetchPublicKeyForApId(user: MiRemoteUser): Promise { // Don't re-fetch if we've updated the user recently - const maxUpdatedTime = Date.now() - 60_000; // 1 minute ago + const maxUpdatedTime = this.timeService.now - 60_000; // 1 minute ago if ( (user.lastFetchedAt && user.lastFetchedAt.valueOf() > maxUpdatedTime) || (user.updatedAt && user.updatedAt.valueOf() > maxUpdatedTime) || From 0b6f0294676df24eb6ed29496f345f3e486f7dc6 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 7 Nov 2025 02:56:18 -0500 Subject: [PATCH 5/7] fix rebase errors --- packages/backend/src/core/activitypub/ApDbResolverService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index cae8f6ad44..4bd4792fce 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -10,7 +10,7 @@ import type { Config } from '@/config.js'; import { MemoryKVCache } from '@/misc/cache.js'; import type { MiUserPublickey } from '@/models/UserPublickey.js'; import { CacheService } from '@/core/CacheService.js'; -import { TimeService } from '@/core/TimeService.js'; +import { TimeService } from '@/global/TimeService.js'; import { UtilityService } from '@/core/UtilityService.js'; import type { MiNote } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; From 36835b4a2e7f3353d8435785a085d252263e56f3 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 14 Nov 2025 19:32:03 -0500 Subject: [PATCH 6/7] fix last time limit for public key refetch --- packages/backend/src/core/activitypub/ApDbResolverService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index 4bd4792fce..d9a8d545b7 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -130,9 +130,9 @@ export class ApDbResolverService implements OnApplicationShutdown { @bindThis public async refetchPublicKeyForApId(user: MiRemoteUser): Promise { // Don't re-fetch if we've updated the user recently - const maxUpdatedTime = this.timeService.now - 60_000; // 1 minute ago if ( (user.lastFetchedAt && user.lastFetchedAt.valueOf() > maxUpdatedTime) || + const maxUpdatedTime = this.timeService.now - (1000 * 60 * 60); // 1 hour (user.updatedAt && user.updatedAt.valueOf() > maxUpdatedTime) || this.idService.parse(user.id).date.valueOf() > maxUpdatedTime ) { From 61ea5559436a945f389efc6fc50f6f20b6455ed5 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 14 Nov 2025 19:32:36 -0500 Subject: [PATCH 7/7] fix refetchPublicKeyForApId always proceeding even if the timeout fails --- .../src/core/activitypub/ApDbResolverService.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts index d9a8d545b7..a5259b1c4a 100644 --- a/packages/backend/src/core/activitypub/ApDbResolverService.ts +++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts @@ -129,20 +129,21 @@ export class ApDbResolverService implements OnApplicationShutdown { */ @bindThis public async refetchPublicKeyForApId(user: MiRemoteUser): Promise { + const oldKey = await this.apPersonService.findPublicKeyByUserId(user.id); + // Don't re-fetch if we've updated the user recently - if ( - (user.lastFetchedAt && user.lastFetchedAt.valueOf() > maxUpdatedTime) || const maxUpdatedTime = this.timeService.now - (1000 * 60 * 60); // 1 hour + if ((user.lastFetchedAt && user.lastFetchedAt.valueOf() > maxUpdatedTime) || (user.updatedAt && user.updatedAt.valueOf() > maxUpdatedTime) || this.idService.parse(user.id).date.valueOf() > maxUpdatedTime ) { this.apLoggerService.logger.debug(`Not updating public key for user ${user.id} (${user.uri}): already checked recently`); - } else { - this.apLoggerService.logger.debug(`Updating public key for user ${user.id} (${user.uri})`); + return oldKey; } - // findPublicKeyByUserId pulls from a cache, but updatePerson also updates that cache if there's any changes. - const oldKey = await this.apPersonService.findPublicKeyByUserId(user.id); + this.apLoggerService.logger.debug(`Updating public key for user ${user.id} (${user.uri})`); + + // updatePerson will update the public key cache if there's any changes. await this.apPersonService.updatePerson(user.uri); const newKey = await this.apPersonService.findPublicKeyByUserId(user.id);