From 03b673165ff98c0c603046a648968f94e9383505 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sat, 3 Dec 2022 23:07:33 +0100 Subject: [PATCH] server: refactor "authUser" functions into separate file They did not really fit into the DbResolver because they may fetch data from remote instances even though DbResolver is only supposed to access the database. --- .../backend/src/queue/processors/inbox.ts | 71 +++++++------------ .../src/remote/activitypub/db-resolver.ts | 50 +------------ .../src/remote/activitypub/misc/auth-user.ts | 50 +++++++++++++ 3 files changed, 77 insertions(+), 94 deletions(-) create mode 100644 packages/backend/src/remote/activitypub/misc/auth-user.ts 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); +}