fetchPersonWithRenewal
This commit is contained in:
parent
a5cccf3799
commit
54fe8ca600
|
@ -40,6 +40,7 @@ type NodeInfo = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FetchInstanceMetadataService {
|
export class FetchInstanceMetadataService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
private httpColon = 'https://';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
|
@ -49,6 +50,7 @@ export class FetchInstanceMetadataService {
|
||||||
private redisClient: Redis.Redis,
|
private redisClient: Redis.Redis,
|
||||||
) {
|
) {
|
||||||
this.logger = this.loggerService.getLogger('metadata', 'cyan');
|
this.logger = this.loggerService.getLogger('metadata', 'cyan');
|
||||||
|
this.httpColon = 'http://';
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -72,8 +74,7 @@ export class FetchInstanceMetadataService {
|
||||||
const _instance = await this.federatedInstanceService.fetch(host);
|
const _instance = await this.federatedInstanceService.fetch(host);
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 3)) {
|
if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 3)) {
|
||||||
// unlock at the finally caluse
|
throw new Error('Skip because updated recently');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +120,12 @@ export class FetchInstanceMetadataService {
|
||||||
await this.federatedInstanceService.update(instance.id, updates);
|
await this.federatedInstanceService.update(instance.id, updates);
|
||||||
|
|
||||||
this.logger.succ(`Successfuly updated metadata of ${instance.host}`);
|
this.logger.succ(`Successfuly updated metadata of ${instance.host}`);
|
||||||
|
this.logger.debug('Updated metadata:', {
|
||||||
|
info: !!info,
|
||||||
|
dom: !!dom,
|
||||||
|
manifest: !!manifest,
|
||||||
|
updates,
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
this.logger.error(`Failed to update metadata of ${instance.host}: ${e}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -131,7 +138,7 @@ export class FetchInstanceMetadataService {
|
||||||
this.logger.info(`Fetching nodeinfo of ${instance.host} ...`);
|
this.logger.info(`Fetching nodeinfo of ${instance.host} ...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo')
|
const wellknown = await this.httpRequestService.getJson(this.httpColon + instance.host + '/.well-known/nodeinfo')
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
if (err.statusCode === 404) {
|
if (err.statusCode === 404) {
|
||||||
throw new Error('No nodeinfo provided');
|
throw new Error('No nodeinfo provided');
|
||||||
|
@ -174,7 +181,7 @@ export class FetchInstanceMetadataService {
|
||||||
private async fetchDom(instance: MiInstance): Promise<DOMWindow['document']> {
|
private async fetchDom(instance: MiInstance): Promise<DOMWindow['document']> {
|
||||||
this.logger.info(`Fetching HTML of ${instance.host} ...`);
|
this.logger.info(`Fetching HTML of ${instance.host} ...`);
|
||||||
|
|
||||||
const url = 'https://' + instance.host;
|
const url = this.httpColon + instance.host;
|
||||||
|
|
||||||
const html = await this.httpRequestService.getHtml(url);
|
const html = await this.httpRequestService.getHtml(url);
|
||||||
|
|
||||||
|
@ -186,7 +193,7 @@ export class FetchInstanceMetadataService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchManifest(instance: MiInstance): Promise<Record<string, unknown> | null> {
|
private async fetchManifest(instance: MiInstance): Promise<Record<string, unknown> | null> {
|
||||||
const url = 'https://' + instance.host;
|
const url = this.httpColon + instance.host;
|
||||||
|
|
||||||
const manifestUrl = url + '/manifest.json';
|
const manifestUrl = url + '/manifest.json';
|
||||||
|
|
||||||
|
@ -197,7 +204,7 @@ export class FetchInstanceMetadataService {
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchFaviconUrl(instance: MiInstance, doc: DOMWindow['document'] | null): Promise<string | null> {
|
private async fetchFaviconUrl(instance: MiInstance, doc: DOMWindow['document'] | null): Promise<string | null> {
|
||||||
const url = 'https://' + instance.host;
|
const url = this.httpColon + instance.host;
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
// https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043
|
// https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043
|
||||||
|
@ -224,12 +231,12 @@ export class FetchInstanceMetadataService {
|
||||||
@bindThis
|
@bindThis
|
||||||
private async fetchIconUrl(instance: MiInstance, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
private async fetchIconUrl(instance: MiInstance, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
|
||||||
if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
|
if (manifest && manifest.icons && manifest.icons.length > 0 && manifest.icons[0].src) {
|
||||||
const url = 'https://' + instance.host;
|
const url = this.httpColon + instance.host;
|
||||||
return (new URL(manifest.icons[0].src, url)).href;
|
return (new URL(manifest.icons[0].src, url)).href;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
const url = 'https://' + instance.host;
|
const url = this.httpColon + instance.host;
|
||||||
|
|
||||||
// https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043
|
// https://github.com/misskey-dev/misskey/pull/8220#issuecomment-1025104043
|
||||||
const links = Array.from(doc.getElementsByTagName('link')).reverse();
|
const links = Array.from(doc.getElementsByTagName('link')).reverse();
|
||||||
|
|
|
@ -122,7 +122,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
|
||||||
user: MiRemoteUser;
|
user: MiRemoteUser;
|
||||||
key: MiUserPublickey | null;
|
key: MiUserPublickey | null;
|
||||||
} | null> {
|
} | null> {
|
||||||
const user = await this.apPersonService.resolvePerson(uri) as MiRemoteUser;
|
const user = await this.apPersonService.resolvePerson(uri, undefined, true) as MiRemoteUser;
|
||||||
if (user.isDeleted) return null;
|
if (user.isDeleted) return null;
|
||||||
|
|
||||||
const keys = await this.publicKeyByUserIdCache.fetch(
|
const keys = await this.publicKeyByUserIdCache.fetch(
|
||||||
|
|
|
@ -36,7 +36,6 @@ import { ApResolverService } from './ApResolverService.js';
|
||||||
import { ApAudienceService } from './ApAudienceService.js';
|
import { ApAudienceService } from './ApAudienceService.js';
|
||||||
import { ApPersonService } from './models/ApPersonService.js';
|
import { ApPersonService } from './models/ApPersonService.js';
|
||||||
import { ApQuestionService } from './models/ApQuestionService.js';
|
import { ApQuestionService } from './models/ApQuestionService.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
|
||||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
import type { Resolver } from './ApResolverService.js';
|
import type { Resolver } from './ApResolverService.js';
|
||||||
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js';
|
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js';
|
||||||
|
@ -109,15 +108,6 @@ export class ApInboxService {
|
||||||
} else {
|
} else {
|
||||||
await this.performOneActivity(actor, activity);
|
await this.performOneActivity(actor, activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ついでにリモートユーザーの情報が古かったら更新しておく
|
|
||||||
if (actor.uri) {
|
|
||||||
if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
|
|
||||||
setImmediate(() => {
|
|
||||||
this.apPersonService.updatePerson(actor.uri);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
|
|
@ -107,16 +107,24 @@ export class ApRequestService {
|
||||||
@bindThis
|
@bindThis
|
||||||
public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown, level: string): Promise<void> {
|
public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown, level: string): Promise<void> {
|
||||||
const body = typeof object === 'string' ? object : JSON.stringify(object);
|
const body = typeof object === 'string' ? object : JSON.stringify(object);
|
||||||
|
const key = await this.getPrivateKey(user.id, level);
|
||||||
const req = createSignedPost({
|
const req = createSignedPost({
|
||||||
level,
|
level,
|
||||||
key: await this.getPrivateKey(user.id, level),
|
key,
|
||||||
url,
|
url,
|
||||||
body,
|
body,
|
||||||
additionalHeaders: {
|
additionalHeaders: {
|
||||||
|
'User-Agent': this.config.userAgent,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.logger.debug('create signed post', {
|
||||||
|
version: 'draft',
|
||||||
|
level,
|
||||||
|
url,
|
||||||
|
keyId: key.keyId,
|
||||||
|
});
|
||||||
|
|
||||||
await this.httpRequestService.send(url, {
|
await this.httpRequestService.send(url, {
|
||||||
method: req.request.method,
|
method: req.request.method,
|
||||||
headers: req.request.headers,
|
headers: req.request.headers,
|
||||||
|
@ -131,14 +139,23 @@ export class ApRequestService {
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async signedGet(url: string, user: { id: MiUser['id'] }, level: string): Promise<unknown> {
|
public async signedGet(url: string, user: { id: MiUser['id'] }, level: string): Promise<unknown> {
|
||||||
|
const key = await this.getPrivateKey(user.id, level);
|
||||||
const req = createSignedGet({
|
const req = createSignedGet({
|
||||||
level,
|
level,
|
||||||
key: await this.getPrivateKey(user.id, level),
|
key,
|
||||||
url,
|
url,
|
||||||
additionalHeaders: {
|
additionalHeaders: {
|
||||||
|
'User-Agent': this.config.userAgent,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.logger.debug('create signed get', {
|
||||||
|
version: 'draft',
|
||||||
|
level,
|
||||||
|
url,
|
||||||
|
keyId: key.keyId,
|
||||||
|
});
|
||||||
|
|
||||||
const res = await this.httpRequestService.send(url, {
|
const res = await this.httpRequestService.send(url, {
|
||||||
method: req.request.method,
|
method: req.request.method,
|
||||||
headers: req.request.headers,
|
headers: req.request.headers,
|
||||||
|
|
|
@ -248,6 +248,22 @@ export class ApPersonService implements OnModuleInit {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
async fetchPersonWithRenewal(uri: string): Promise<MiLocalUser | MiRemoteUser | null> {
|
||||||
|
const exist = await this.fetchPerson(uri);
|
||||||
|
if (exist == null) return null;
|
||||||
|
|
||||||
|
// ついでにリモートユーザーの情報が古かったら更新しておく
|
||||||
|
if (this.userEntityService.isRemoteUser(exist)) {
|
||||||
|
if (exist.lastFetchedAt == null || Date.now() - exist.lastFetchedAt.getTime() > 1000 * 60 * 60 * 3) {
|
||||||
|
await this.updatePerson(exist.uri);
|
||||||
|
return await this.fetchPerson(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exist;
|
||||||
|
}
|
||||||
|
|
||||||
private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any): Promise<Partial<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'avatarUrl' | 'bannerUrl' | 'avatarBlurhash' | 'bannerBlurhash'>>> {
|
private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any): Promise<Partial<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'avatarUrl' | 'bannerUrl' | 'avatarBlurhash' | 'bannerBlurhash'>>> {
|
||||||
if (user == null) throw new Error('failed to create user: user is null');
|
if (user == null) throw new Error('failed to create user: user is null');
|
||||||
|
|
||||||
|
@ -624,9 +640,9 @@ export class ApPersonService implements OnModuleInit {
|
||||||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async resolvePerson(uri: string, resolver?: Resolver): Promise<MiLocalUser | MiRemoteUser> {
|
public async resolvePerson(uri: string, resolver?: Resolver, withRenewal = false): Promise<MiLocalUser | MiRemoteUser> {
|
||||||
//#region このサーバーに既に登録されていたらそれを返す
|
//#region このサーバーに既に登録されていたらそれを返す
|
||||||
const exist = await this.fetchPerson(uri);
|
const exist = withRenewal ? await this.fetchPersonWithRenewal(uri) : await this.fetchPerson(uri);
|
||||||
if (exist) return exist;
|
if (exist) return exist;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ import type { MiRemoteUser } from '@/models/User.js';
|
||||||
import type { MiUserPublickey } from '@/models/UserPublickey.js';
|
import type { MiUserPublickey } from '@/models/UserPublickey.js';
|
||||||
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
|
||||||
import { StatusError } from '@/misc/status-error.js';
|
import { StatusError } from '@/misc/status-error.js';
|
||||||
|
import * as Acct from '@/misc/acct.js';
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
||||||
import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js';
|
import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js';
|
||||||
|
@ -79,7 +80,6 @@ export class InboxProcessorService {
|
||||||
key: MiUserPublickey | null;
|
key: MiUserPublickey | null;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
|
||||||
// keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得
|
|
||||||
try {
|
try {
|
||||||
authUser = await this.apDbResolverService.getAuthUserFromApId(getApId(activity.actor), signature.keyId);
|
authUser = await this.apDbResolverService.getAuthUserFromApId(getApId(activity.actor), signature.keyId);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -103,7 +103,15 @@ export class InboxProcessorService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP-Signatureの検証
|
// HTTP-Signatureの検証
|
||||||
const httpSignatureValidated = verifyDraftSignature(signature, authUser.key.keyPem);
|
const errorLogger = (ms: any) => this.logger.error(ms);
|
||||||
|
const httpSignatureValidated = verifyDraftSignature(signature, authUser.key.keyPem, errorLogger);
|
||||||
|
this.logger.debug('Inbox message validation: ', {
|
||||||
|
userId: authUser.user.id,
|
||||||
|
userAcct: Acct.toString(authUser.user),
|
||||||
|
parsedKeyId: signature.keyId,
|
||||||
|
foundKeyId: authUser.key.keyId,
|
||||||
|
httpSignatureValidated,
|
||||||
|
});
|
||||||
|
|
||||||
// また、signatureのsignerは、activity.actorと一致する必要がある
|
// また、signatureのsignerは、activity.actorと一致する必要がある
|
||||||
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
|
||||||
|
|
Loading…
Reference in New Issue