refactor away as many "new Resolver" as possible #271
19 changed files with 138 additions and 163 deletions
|
@ -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<InboxJobData>): Promise<string> => {
|
|||
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:
|
||||
// <https://example.com/users/user#main-key>
|
||||
// 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})`;
|
||||
}
|
||||
|
@ -156,6 +137,6 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
|
|||
});
|
||||
|
||||
// アクティビティを処理
|
||||
await perform(authUser.user, activity);
|
||||
await perform(authUser.user, activity, resolver);
|
||||
return 'ok';
|
||||
};
|
||||
|
|
|
@ -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<UserPublickey>(
|
||||
Infinity,
|
||||
(keyId) => UserPublickeys.findOneBy({ keyId }).then(x => x ?? undefined),
|
||||
);
|
||||
const publicKeyByUserIdCache = new Cache<UserPublickey>(
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,11 @@ import Resolver from '@/remote/activitypub/resolver.js';
|
|||
import { IAccept, isFollow, getApType } from '@/remote/activitypub/type.js';
|
||||
import acceptFollow from './follow.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IAccept): Promise<string> => {
|
||||
export default async (actor: CacheableRemoteUser, activity: IAccept, resolver: Resolver): Promise<string> => {
|
||||
const uri = activity.id || activity;
|
||||
|
||||
apLogger.info(`Accept: ${uri}`);
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
apLogger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
|
|
|
@ -4,13 +4,11 @@ import Resolver from '@/remote/activitypub/resolver.js';
|
|||
import { IAnnounce, getApId } from '@/remote/activitypub/type.js';
|
||||
import announceNote from './note.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IAnnounce): Promise<void> => {
|
||||
export default async (actor: CacheableRemoteUser, activity: IAnnounce, resolver: Resolver): Promise<void> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
apLogger.info(`Announce: ${uri}`);
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const targetUri = getApId(activity.object);
|
||||
|
||||
announceNote(resolver, actor, activity, targetUri);
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ICreate, getApId, isPost, getApType } from '../../type.js';
|
|||
import { apLogger } from '../../logger.js';
|
||||
import createNote from './note.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: ICreate): Promise<void> => {
|
||||
export default async (actor: CacheableRemoteUser, activity: ICreate, resolver: Resolver): Promise<void> => {
|
||||
const uri = getApId(activity);
|
||||
|
||||
apLogger.info(`Create: ${uri}`);
|
||||
|
@ -26,8 +26,6 @@ export default async (actor: CacheableRemoteUser, activity: ICreate): Promise<vo
|
|||
activity.object.attributedTo = activity.actor;
|
||||
}
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
apLogger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
|
|
|
@ -18,13 +18,12 @@ import remove from './remove/index.js';
|
|||
import block from './block/index.js';
|
||||
import flag from './flag/index.js';
|
||||
|
||||
export async function performActivity(actor: CacheableRemoteUser, activity: IObject) {
|
||||
export async function performActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver) {
|
||||
if (isCollectionOrOrderedCollection(activity)) {
|
||||
const resolver = new Resolver();
|
||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
|
||||
const act = await resolver.resolve(item);
|
||||
try {
|
||||
await performOneActivity(actor, act);
|
||||
await performOneActivity(actor, act, resolver);
|
||||
} catch (err) {
|
||||
if (err instanceof Error || typeof err === 'string') {
|
||||
apLogger.error(err);
|
||||
|
@ -32,37 +31,37 @@ export async function performActivity(actor: CacheableRemoteUser, activity: IObj
|
|||
}
|
||||
}
|
||||
} else {
|
||||
await performOneActivity(actor, activity);
|
||||
await performOneActivity(actor, activity, resolver);
|
||||
}
|
||||
}
|
||||
|
||||
async function performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise<void> {
|
||||
async function performOneActivity(actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise<void> {
|
||||
if (actor.isSuspended) return;
|
||||
|
||||
if (isCreate(activity)) {
|
||||
await create(actor, activity);
|
||||
await create(actor, activity, resolver);
|
||||
} else if (isDelete(activity)) {
|
||||
await performDeleteActivity(actor, activity);
|
||||
} else if (isUpdate(activity)) {
|
||||
await performUpdateActivity(actor, activity);
|
||||
await performUpdateActivity(actor, activity, resolver);
|
||||
} else if (isRead(activity)) {
|
||||
await performReadActivity(actor, activity);
|
||||
} else if (isFollow(activity)) {
|
||||
await follow(actor, activity);
|
||||
} else if (isAccept(activity)) {
|
||||
await accept(actor, activity);
|
||||
await accept(actor, activity, resolver);
|
||||
} else if (isReject(activity)) {
|
||||
await reject(actor, activity);
|
||||
await reject(actor, activity, resolver);
|
||||
} else if (isAdd(activity)) {
|
||||
await add(actor, activity).catch(err => apLogger.error(err));
|
||||
} else if (isRemove(activity)) {
|
||||
await remove(actor, activity).catch(err => apLogger.error(err));
|
||||
} else if (isAnnounce(activity)) {
|
||||
await announce(actor, activity);
|
||||
await announce(actor, activity, resolver);
|
||||
} else if (isLike(activity)) {
|
||||
await like(actor, activity);
|
||||
} else if (isUndo(activity)) {
|
||||
await undo(actor, activity);
|
||||
await undo(actor, activity, resolver);
|
||||
} else if (isBlock(activity)) {
|
||||
await block(actor, activity);
|
||||
} else if (isFlag(activity)) {
|
||||
|
|
|
@ -4,13 +4,11 @@ import { IReject, isFollow, getApType } from '../../type.js';
|
|||
import Resolver from '../../resolver.js';
|
||||
import rejectFollow from './follow.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IReject): Promise<string> => {
|
||||
export default async (actor: CacheableRemoteUser, activity: IReject, resolver: Resolver): Promise<string> => {
|
||||
const uri = activity.id || activity;
|
||||
|
||||
apLogger.info(`Reject: ${uri}`);
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
apLogger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
|
|
|
@ -8,7 +8,7 @@ import undoLike from './like.js';
|
|||
import undoAccept from './accept.js';
|
||||
import { undoAnnounce } from './announce.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IUndo): Promise<string> => {
|
||||
export default async (actor: CacheableRemoteUser, activity: IUndo, resolver: Resolver): Promise<string> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ export default async (actor: CacheableRemoteUser, activity: IUndo): Promise<stri
|
|||
|
||||
apLogger.info(`Undo: ${uri}`);
|
||||
|
||||
const resolver = new Resolver();
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
apLogger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
|
|
|
@ -8,15 +8,13 @@ import { updatePerson } from '@/remote/activitypub/models/person.js';
|
|||
/**
|
||||
* Updateアクティビティを捌きます
|
||||
*/
|
||||
export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<string> => {
|
||||
export default async (actor: CacheableRemoteUser, activity: IUpdate, resolver: Resolver): Promise<string> => {
|
||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||
return 'skip: invalid actor';
|
||||
}
|
||||
|
||||
apLogger.debug('Update');
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const object = await resolver.resolve(activity.object).catch(e => {
|
||||
apLogger.error(`Resolution failed: ${e}`);
|
||||
throw e;
|
||||
|
|
50
packages/backend/src/remote/activitypub/misc/auth-user.ts
Normal file
50
packages/backend/src/remote/activitypub/misc/auth-user.ts
Normal file
|
@ -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<UserPublickey>(
|
||||
Infinity,
|
||||
(keyId) => UserPublickeys.findOneBy({ keyId }).then(x => x ?? undefined),
|
||||
);
|
||||
const publicKeyByUserIdCache = new Cache<UserPublickey>(
|
||||
Infinity,
|
||||
(userId) => UserPublickeys.findOneBy({ userId }).then(x => x ?? undefined),
|
||||
);
|
||||
|
||||
function authUserFromApId(uri: string): Promise<AuthUser | null> {
|
||||
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<AuthUser | null> {
|
||||
let authUser = await publicKeyCache.fetch(keyId)
|
||||
.then(async 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);
|
||||
}
|
|
@ -11,13 +11,13 @@ import { apLogger } from '../logger.js';
|
|||
/**
|
||||
* Imageを作成します。
|
||||
*/
|
||||
export async function createImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
||||
export async function createImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise<DriveFile> {
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
throw new Error('actor has been suspended');
|
||||
}
|
||||
|
||||
const image = await new Resolver().resolve(value) as any;
|
||||
const image = await resolver.resolve(value) as any;
|
||||
|
||||
if (image.url == null) {
|
||||
throw new Error('invalid image: url not privided');
|
||||
|
@ -58,9 +58,9 @@ export async function createImage(actor: CacheableRemoteUser, value: any): Promi
|
|||
* If the target Image is registered in FoundKey, return it; otherwise, fetch it from the remote server and return it.
|
||||
* Fetch the image from the remote server, register it in FoundKey and return it.
|
||||
*/
|
||||
export async function resolveImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
||||
export async function resolveImage(actor: CacheableRemoteUser, value: any, resolver: Resolver): Promise<DriveFile> {
|
||||
// TODO
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
return await createImage(actor, value);
|
||||
// Fetch from remote server and register it.
|
||||
return await createImage(actor, value, resolver);
|
||||
}
|
||||
|
|
|
@ -5,11 +5,9 @@ import { IObject, isMention, IApMention } from '../type.js';
|
|||
import Resolver from '../resolver.js';
|
||||
import { resolvePerson } from './person.js';
|
||||
|
||||
export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
|
||||
export async function extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) {
|
||||
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
|
||||
|
||||
const resolver = new Resolver();
|
||||
|
||||
const limit = promiseLimit<CacheableUser | null>(2);
|
||||
const mentionedUsers = (await Promise.all(
|
||||
hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))),
|
||||
|
|
|
@ -63,7 +63,7 @@ export async function fetchNote(object: string | IObject): Promise<Note | null>
|
|||
/**
|
||||
* Noteを作成します。
|
||||
*/
|
||||
export async function createNote(value: string | IObject, resolver?: Resolver = new Resolver(), silent = false): Promise<Note | null> {
|
||||
export async function createNote(value: string | IObject, resolver: Resolver, silent = false): Promise<Note | null> {
|
||||
const object: any = await resolver.resolve(value);
|
||||
|
||||
const entryUri = getApId(value);
|
||||
|
@ -107,7 +107,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver =
|
|||
|
||||
let isTalk = note._misskey_talk && visibility === 'specified';
|
||||
|
||||
const apMentions = await extractApMentions(note.tag);
|
||||
const apMentions = await extractApMentions(note.tag, resolver);
|
||||
const apHashtags = await extractApHashtags(note.tag);
|
||||
|
||||
// 添付ファイル
|
||||
|
@ -119,7 +119,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver =
|
|||
note.attachment = Array.isArray(note.attachment) ? note.attachment : note.attachment ? [note.attachment] : [];
|
||||
const files = note.attachment
|
||||
.map(attach => attach.sensitive = note.sensitive)
|
||||
? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x)) as Promise<DriveFile>)))
|
||||
? (await Promise.all(note.attachment.map(x => limit(() => resolveImage(actor, x, resolver)) as Promise<DriveFile>)))
|
||||
.filter(image => image != null)
|
||||
: [];
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise<Cac
|
|||
/**
|
||||
* Personを作成します。
|
||||
*/
|
||||
export async function createPerson(uri: string, resolver?: Resolver = new Resolver()): Promise<User> {
|
||||
export async function createPerson(uri: string, resolver: Resolver): Promise<User> {
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
||||
if (uri.startsWith(config.url)) {
|
||||
|
@ -238,7 +238,7 @@ export async function createPerson(uri: string, resolver?: Resolver = new Resolv
|
|||
].map(img =>
|
||||
img == null
|
||||
? Promise.resolve(null)
|
||||
: resolveImage(user!, img).catch(() => null),
|
||||
: resolveImage(user!, img, resolver).catch(() => null),
|
||||
));
|
||||
|
||||
const avatarId = avatar ? avatar.id : null;
|
||||
|
@ -278,7 +278,7 @@ export async function createPerson(uri: string, resolver?: Resolver = new Resolv
|
|||
* @param resolver Resolver
|
||||
* @param hint Hint of Person object (If this value is a valid Person, it is used for updating without Remote resolve.)
|
||||
*/
|
||||
export async function updatePerson(uri: string, resolver?: Resolver = new Resolver(), hint?: IObject): Promise<void> {
|
||||
export async function updatePerson(uri: string, resolver: Resolver, hint?: IObject): Promise<void> {
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
||||
// URIがこのサーバーを指しているならスキップ
|
||||
|
@ -307,7 +307,7 @@ export async function updatePerson(uri: string, resolver?: Resolver = new Resolv
|
|||
].map(img =>
|
||||
img == null
|
||||
? Promise.resolve(null)
|
||||
: resolveImage(exist, img).catch(() => null),
|
||||
: resolveImage(exist, img, resolver).catch(() => null),
|
||||
));
|
||||
|
||||
// カスタム絵文字取得
|
||||
|
@ -386,7 +386,7 @@ export async function updatePerson(uri: string, resolver?: Resolver = new Resolv
|
|||
* If the target Person is registered in FoundKey, return it; otherwise, fetch it from a remote server and return it.
|
||||
* Fetch the person from the remote server, register it in FoundKey, and return it.
|
||||
*/
|
||||
export async function resolvePerson(uri: string, resolver?: Resolver): Promise<CacheableUser> {
|
||||
export async function resolvePerson(uri: string, resolver: Resolver): Promise<CacheableUser> {
|
||||
if (typeof uri !== 'string') throw new Error('uri is not string');
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
|
@ -398,7 +398,7 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<C
|
|||
//#endregion
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
return await createPerson(uri, resolver ?? new Resolver());
|
||||
return await createPerson(uri, resolver);
|
||||
}
|
||||
|
||||
const services: {
|
||||
|
@ -455,15 +455,13 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined)
|
|||
return { fields, services };
|
||||
}
|
||||
|
||||
export async function updateFeatured(userId: User['id'], resolver?: Resolver) {
|
||||
async function updateFeatured(userId: User['id'], resolver: Resolver) {
|
||||
const user = await Users.findOneByOrFail({ id: userId });
|
||||
if (!Users.isRemoteUser(user)) return;
|
||||
if (!user.featured) return;
|
||||
|
||||
apLogger.info(`Updating the featured: ${user.uri}`);
|
||||
|
||||
if (resolver == null) resolver = new Resolver();
|
||||
|
||||
// Resolve to (Ordered)Collection Object
|
||||
const collection = await resolver.resolveCollection(user.featured);
|
||||
if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection');
|
||||
|
|
|
@ -5,7 +5,7 @@ import Resolver from '../resolver.js';
|
|||
import { IObject, IQuestion, isQuestion } from '../type.js';
|
||||
import { apLogger } from '../logger.js';
|
||||
|
||||
export async function extractPollFromQuestion(source: string | IObject, resolver?: Resolver = new Resolver()): Promise<IPoll> {
|
||||
export async function extractPollFromQuestion(source: string | IObject, resolver: Resolver): Promise<IPoll> {
|
||||
const question = await resolver.resolve(source);
|
||||
|
||||
if (!isQuestion(question)) {
|
||||
|
@ -39,7 +39,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver
|
|||
* @param resolver Resolver to use
|
||||
* @returns true if updated
|
||||
*/
|
||||
export async function updateQuestion(value: string | IObject, resolver?: Resolver = new Resolver()) {
|
||||
export async function updateQuestion(value: string | IObject, resolver: Resolver) {
|
||||
const uri = typeof value === 'string' ? value : value.id;
|
||||
|
||||
// URIがこのサーバーを指しているならスキップ
|
||||
|
|
|
@ -3,15 +3,16 @@ import { CacheableRemoteUser } from '@/models/entities/user.js';
|
|||
import { IObject } from './type.js';
|
||||
import { performActivity } from './kernel/index.js';
|
||||
import { updatePerson } from './models/person.js';
|
||||
import Resolver from './resolver.js';
|
||||
|
||||
export default async (actor: CacheableRemoteUser, activity: IObject): Promise<void> => {
|
||||
await performActivity(actor, activity);
|
||||
export default async (actor: CacheableRemoteUser, activity: IObject, resolver: Resolver): Promise<void> => {
|
||||
await performActivity(actor, activity, resolver);
|
||||
|
||||
// And while I'm at it, I'll update the remote user information if it's out of date.
|
||||
if (actor.uri) {
|
||||
if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > DAY) {
|
||||
setImmediate(() => {
|
||||
updatePerson(actor.uri!);
|
||||
updatePerson(actor.uri!, resolver);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,11 @@ import { Users } from '@/models/index.js';
|
|||
import webFinger from './webfinger.js';
|
||||
import { createPerson, updatePerson } from './activitypub/models/person.js';
|
||||
import { remoteLogger } from './logger.js';
|
||||
import Resolver from './activitypub/resolver.js';
|
||||
|
||||
const logger = remoteLogger.createSubLogger('resolve-user');
|
||||
|
||||
export async function resolveUser(username: string, idnHost: string | null): Promise<User> {
|
||||
export async function resolveUser(username: string, idnHost: string | null, resolver: Resolver = new Resolver()): Promise<User> {
|
||||
const usernameLower = username.toLowerCase();
|
||||
|
||||
if (idnHost == null) {
|
||||
|
@ -47,7 +48,7 @@ export async function resolveUser(username: string, idnHost: string | null): Pro
|
|||
const self = await resolveSelf(acctLower);
|
||||
|
||||
logger.succ(`return new remote user: ${chalk.magenta(acctLower)}`);
|
||||
return await createPerson(self.href);
|
||||
return await createPerson(self, resolver);
|
||||
}
|
||||
|
||||
// If user information is out of date, start over with webfinger
|
||||
|
@ -60,13 +61,13 @@ export async function resolveUser(username: string, idnHost: string | null): Pro
|
|||
logger.info(`try resync: ${acctLower}`);
|
||||
const self = await resolveSelf(acctLower);
|
||||
|
||||
if (user.uri !== self.href) {
|
||||
if (user.uri !== self) {
|
||||
// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
|
||||
logger.info(`uri missmatch: ${acctLower}`);
|
||||
logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`);
|
||||
logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self}`);
|
||||
|
||||
// validate uri
|
||||
const uri = new URL(self.href);
|
||||
const uri = new URL(self);
|
||||
if (uri.hostname !== host) {
|
||||
throw new Error('Invalid uri');
|
||||
}
|
||||
|
@ -75,16 +76,16 @@ export async function resolveUser(username: string, idnHost: string | null): Pro
|
|||
usernameLower,
|
||||
host,
|
||||
}, {
|
||||
uri: self.href,
|
||||
uri: self,
|
||||
});
|
||||
} else {
|
||||
logger.info(`uri is fine: ${acctLower}`);
|
||||
}
|
||||
|
||||
await updatePerson(self.href);
|
||||
await updatePerson(self, resolver);
|
||||
|
||||
logger.info(`return resynced remote user: ${acctLower}`);
|
||||
return await Users.findOneBy({ uri: self.href }).then(u => {
|
||||
return await Users.findOneBy({ uri: self }).then(u => {
|
||||
if (u == null) {
|
||||
throw new Error('user not found');
|
||||
} else {
|
||||
|
@ -97,16 +98,21 @@ export async function resolveUser(username: string, idnHost: string | null): Pro
|
|||
return user;
|
||||
}
|
||||
|
||||
async function resolveSelf(acctLower: string) {
|
||||
/**
|
||||
* Gets the Webfinger href matching rel="self".
|
||||
*/
|
||||
async function resolveSelf(acctLower: string): string {
|
||||
logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
|
||||
// get webfinger response for user
|
||||
const finger = await webFinger(acctLower).catch(e => {
|
||||
logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`);
|
||||
throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`);
|
||||
});
|
||||
const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self');
|
||||
if (!self) {
|
||||
// try to find the rel="self" link
|
||||
const self = finger.links.find(link => link.rel?.toLowerCase() === 'self');
|
||||
if (!self?.href) {
|
||||
logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`);
|
||||
throw new Error('self link not found');
|
||||
}
|
||||
return self;
|
||||
return self.href;
|
||||
}
|
||||
|
|
|
@ -114,8 +114,8 @@ async function fetchAny(uri: string, me: CacheableLocalUser | null | undefined):
|
|||
|
||||
return await mergePack(
|
||||
me,
|
||||
isActor(object) ? await createPerson(getApId(object)) : null,
|
||||
isPost(object) ? await createNote(getApId(object), undefined, true) : null,
|
||||
isActor(object) ? await createPerson(getApId(object), resolver) : null,
|
||||
isPost(object) ? await createNote(getApId(object), resolver, true) : null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import Resolver from '@/remote/activitypub/resolver.js';
|
||||
import { updatePerson } from '@/remote/activitypub/models/person.js';
|
||||
import define from '../../define.js';
|
||||
import { getRemoteUser } from '../../common/getters.js';
|
||||
|
@ -19,5 +20,5 @@ export const paramDef = {
|
|||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
const user = await getRemoteUser(ps.userId);
|
||||
await updatePerson(user.uri!);
|
||||
await updatePerson(user.uri!, new Resolver());
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue