fix(backend): handle relay-delivered Announce activities correctly (#17308)

* fix(backend): handle relay-delivered Announce activities correctly

Relay Announce activities now use the target note URI instead of the
Announce URI for federation allowlist checks, dedup locking, and
existence lookups. Notes delivered via relay are published directly to
the notes stream without creating a renote.

Closes #11056

* Update packages/backend/src/core/RelayService.ts

Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>

---------

Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
Jaehong Kang 2026-04-15 23:05:36 +09:00 committed by GitHub
commit 277a1ef31f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 30 additions and 5 deletions

View file

@ -16,6 +16,7 @@
- Fix: ローカルに存在しないリモートアカウントに対するアカウント削除リクエストを受信した際に、そのユーザーを新規作成して削除する挙動を修正
- Fix: Inboxでの特定のエラーによる失敗はDelayedにしない
- Fix: ID生成アルゴリズムにULIDを使用している場合にMisskeyが正しく動作しない問題を修正
- Fix: リレー経由で届いたノートがリノートとして表示される問題を修正
- Fix: robots.txtの内容を調整
## 2026.3.2

View file

@ -91,13 +91,27 @@ export class RelayService {
return JSON.stringify(result);
}
@bindThis
private getAcceptedRelays(): Promise<MiRelay[]> {
return this.relaysCache.fetch(() => this.relaysRepository.findBy({
status: 'accepted',
}));
}
@bindThis
public async isRelayActor(actor: { inbox: string | null; sharedInbox: string | null }): Promise<boolean> {
const relays = await this.getAcceptedRelays();
return relays.some(relay =>
(actor.inbox != null && relay.inbox === actor.inbox)
|| (actor.sharedInbox != null && relay.inbox === actor.sharedInbox),
);
}
@bindThis
public async deliverToRelays(user: { id: MiUser['id']; host: null; }, activity: any): Promise<void> {
if (activity == null) return;
const relays = await this.relaysCache.fetch(() => this.relaysRepository.findBy({
status: 'accepted',
}));
const relays = await this.getAcceptedRelays();
if (relays.length === 0) return;
const copy = deepClone(activity);

View file

@ -302,12 +302,14 @@ export class ApInboxService {
@bindThis
private async announceNote(actor: MiRemoteUser, activity: IAnnounce, target: IPost, resolver?: Resolver): Promise<string | void> {
const uri = getApId(activity);
if (actor.isSuspended) {
return;
}
// リレーからのAnnounceかチェック
const fromRelay = await this.relayService.isRelayActor(actor);
const uri = getApId(fromRelay ? target : activity);
// アナウンス先が許可されているかチェック
if (!this.utilityService.isFederationAllowedUri(uri)) return;
@ -336,6 +338,14 @@ export class ApInboxService {
throw err;
}
// リレーからのAnnounceはリートを作成せず、ートを直接公開する
if (fromRelay) {
this.logger.info(`Publishing relay-delivered note: ${uri}`);
const noteObj = await this.noteEntityService.pack(renote, null, { skipHide: true, withReactionAndUserPairCache: true });
this.globalEventService.publishNotesStream(noteObj);
return;
}
if (!await this.noteEntityService.isVisibleForMe(renote, actor.id)) {
return 'skip: invalid actor for this activity';
}