import config from "../../config/index.js"; import { Notes, Users, UserPublickeys, MessagingMessages } from "../../models/index.js"; import { Cache } from "../../misc/cache.js"; import { uriPersonCache, userByIdCache } from "../../services/user-cache.js"; import { getApId } from "./type.js"; import { resolvePerson, updatePerson } from "./models/person.js"; import { subscriber } from "../../db/redis.js"; import { toPuny } from "../../misc/convert-host.js"; const publicKeyCache = new Cache("publicKey", 60 * 30); const publicKeyByUserIdCache = new Cache("publicKeyByUserId", 60 * 30); export function parseUri(value) { const uri = getApId(value); const parsed = new URL(uri); if (toPuny(parsed.host) === toPuny(config.host)) { const localRegex = new RegExp(`^.*?/(\\w+)/(\\w+)(?:/(.+))?`); const matchLocal = uri.match(localRegex); if (matchLocal == null) { throw new Error(`Failed to parse local URI: ${uri}`); } return { local: true, type: matchLocal[1], id: matchLocal[2], rest: matchLocal[3] }; } else { return { local: false, uri }; } } export default class DbResolver { /** * AP Note => Misskey Note in DB */ async getNoteFromApId(value) { const parsed = parseUri(value); if (parsed.local) { if (parsed.type !== "notes") return null; return await Notes.findOneBy({ id: parsed.id }); } else { return await Notes.findOne({ where: [ { uri: parsed.uri }, { url: parsed.uri } ] }); } } async getMessageFromApId(value) { const parsed = parseUri(value); if (parsed.local) { if (parsed.type !== "notes") return null; return await MessagingMessages.findOneBy({ id: parsed.id }); } else { return await MessagingMessages.findOneBy({ uri: parsed.uri }); } } /** * AP Person => Misskey User in DB */ async getUserFromApId(value) { const parsed = parseUri(value); if (parsed.local) { if (parsed.type !== "users") return null; return await userByIdCache.fetchMaybe(parsed.id, ()=>Users.findOneBy({ id: parsed.id }).then((x)=>x ?? undefined), true) ?? null; } else { return await uriPersonCache.fetch(parsed.uri, ()=>Users.findOneBy({ uri: parsed.uri }), true); } } /** * AP KeyId => Misskey User and Key */ async getAuthUserFromKeyId(keyId) { const key = await publicKeyCache.fetch(keyId, async ()=>{ const key = await UserPublickeys.findOneBy({ keyId }); if (key == null) return null; return key; }, true, (key)=>key != null); if (key == null) return null; return { user: await userByIdCache.fetch(key.userId, ()=>Users.findOneByOrFail({ id: key.userId }), true), key }; } /** * AP Actor id => Misskey User and Key */ async getAuthUserFromApId(uri) { const user = await resolvePerson(uri); if (user == null) return null; const key = await publicKeyByUserIdCache.fetch(user.id, ()=>UserPublickeys.findOneBy({ userId: user.id }), true, (v)=>v != null); return { user, key }; } async refetchPublicKeyForApId(user) { try { await updatePerson(user.uri, undefined, undefined, user); let key = await UserPublickeys.findOneBy({ userId: user.id }); if (key != null) { await publicKeyByUserIdCache.set(user.id, key); } return key; } catch { return null; } } } subscriber.on("message", async (_, data)=>{ const obj = JSON.parse(data); if (obj.channel === "internal") { const { type, body } = obj.message; switch(type){ case "remoteUserDeleted": case "localUserDeleted": { const toDelete = Array.from(await publicKeyByUserIdCache.getAll()).filter((v)=>v[1]?.userId === body.id).map((v)=>v[0]); const toDeleteKey = Array.from(await publicKeyCache.getAll()).filter((v)=>v[1]?.userId === body.id).map((v)=>v[0]); await publicKeyByUserIdCache.delete(...toDelete); await publicKeyCache.delete(...toDeleteKey); break; } default: break; } } });