From e00e6c5eb43535240b0e99902be477ed9a09e56e Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 30 Aug 2025 23:15:36 +0900 Subject: [PATCH 1/3] fix: local user profile url cannot be resolved with ap/show --- .../src/server/api/endpoints/ap/show.ts | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 4afed7dc5c..46df43325d 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -21,6 +21,10 @@ import { bindThis } from '@/decorators.js'; import { ApiError } from '../../error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js'; +import * as Acct from '@/misc/acct.js'; +import { IsNull } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import type { NotesRepository, UsersRepository } from '@/models/_.js'; export const meta = { tags: ['federation'], @@ -116,6 +120,9 @@ export default class extends Endpoint { // eslint- private apDbResolverService: ApDbResolverService, private apPersonService: ApPersonService, private apNoteService: ApNoteService, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, ) { super(meta, paramDef, async (ps, me) => { const object = await this.fetchAny(ps.uri, me); @@ -137,7 +144,7 @@ export default class extends Endpoint { // eslint- } let local = await this.mergePack(me, ...await Promise.all([ - this.apDbResolverService.getUserFromApId(uri), + this.apDbResolverService.getUserFromApId(uri).then(async x => x ?? await this.getUserFromProfileUrl(uri)), this.apDbResolverService.getNoteFromApId(uri), ])); if (local != null) return local; @@ -200,6 +207,30 @@ export default class extends Endpoint { // eslint- ); } + // To keep consistency with ap/show from external server, + // this function will handle `https://local.instance/@user` + // profile url to resolve user. + @bindThis + private async getUserFromProfileUrl(url: string): Promise { + if (!this.utilityService.isUriLocal(url)) { + return null; + } + + const uri = new URL(url); + const pathComponents = uri.pathname.split('/').filter(Boolean); + if (pathComponents.length === 1 && pathComponents[0].startsWith('@')) { + const acct = Acct.parse(pathComponents[0]); + + return await this.usersRepository.findOneBy({ + usernameLower: acct.username.toLowerCase(), + host: acct.host ?? IsNull(), + isSuspended: false, + }); + } + + return null; + } + @bindThis private async mergePack(me: MiLocalUser | null | undefined, user: MiUser | null | undefined, note: MiNote | null | undefined): Promise | null> { if (user != null) { From 32eaa31d426a537c2d1ed176ad14d8591b3ef483 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sat, 30 Aug 2025 23:46:34 +0900 Subject: [PATCH 2/3] fix: local user url with hostname could not be resolved --- packages/backend/src/server/api/endpoints/ap/show.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 46df43325d..b367de7800 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -18,6 +18,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; +import type { Config } from '@/config.js'; import { ApiError } from '../../error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js'; @@ -113,6 +114,12 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + private utilityService: UtilityService, private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, @@ -120,9 +127,6 @@ export default class extends Endpoint { // eslint- private apDbResolverService: ApDbResolverService, private apPersonService: ApPersonService, private apNoteService: ApNoteService, - - @Inject(DI.usersRepository) - private usersRepository: UsersRepository, ) { super(meta, paramDef, async (ps, me) => { const object = await this.fetchAny(ps.uri, me); @@ -220,6 +224,8 @@ export default class extends Endpoint { // eslint- const pathComponents = uri.pathname.split('/').filter(Boolean); if (pathComponents.length === 1 && pathComponents[0].startsWith('@')) { const acct = Acct.parse(pathComponents[0]); + // normalize acct host + if (acct.host != null && this.utilityService.toPuny(acct.host) === this.utilityService.toPuny(this.config.host)) acct.host = null; return await this.usersRepository.findOneBy({ usernameLower: acct.username.toLowerCase(), From a5727cb72a3be5ea6f27507242c8e5296505fd8a Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Sun, 31 Aug 2025 00:01:02 +0900 Subject: [PATCH 3/3] chore: use common utility for checking self host --- packages/backend/src/server/api/endpoints/ap/show.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index b367de7800..1fd5d0c0fb 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -18,7 +18,6 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { bindThis } from '@/decorators.js'; -import type { Config } from '@/config.js'; import { ApiError } from '../../error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js'; @@ -114,9 +113,6 @@ export const paramDef = { @Injectable() export default class extends Endpoint { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.config) - private config: Config, - @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -225,7 +221,7 @@ export default class extends Endpoint { // eslint- if (pathComponents.length === 1 && pathComponents[0].startsWith('@')) { const acct = Acct.parse(pathComponents[0]); // normalize acct host - if (acct.host != null && this.utilityService.toPuny(acct.host) === this.utilityService.toPuny(this.config.host)) acct.host = null; + if (this.utilityService.isSelfHost(acct.host)) acct.host = null; return await this.usersRepository.findOneBy({ usernameLower: acct.username.toLowerCase(),