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); +}