APリファクタとLD-Signatureの検証に対応 (#6300)
* DbResolver * inbox types * 認証順を変更 * User/Keyあたりをまとめる * LD-Signatue * Validate contexts url * LD-Signature DocumentLoaderにProxyとTimeout
This commit is contained in:
		
							
								
								
									
										4
									
								
								COPYING
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								COPYING
									
									
									
									
									
								
							@@ -13,3 +13,7 @@ https://github.com/twitter/twemoji-parser/blob/master/LICENSE.md
 | 
				
			|||||||
Emoji keywords for Unicode 11 and below by Mu-An Chiou
 | 
					Emoji keywords for Unicode 11 and below by Mu-An Chiou
 | 
				
			||||||
License: MIT
 | 
					License: MIT
 | 
				
			||||||
https://github.com/muan/emojilib/blob/master/LICENSE
 | 
					https://github.com/muan/emojilib/blob/master/LICENSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RsaSignature2017 implementation by Transmute Industries Inc
 | 
				
			||||||
 | 
					License: MIT
 | 
				
			||||||
 | 
					https://github.com/transmute-industries/RsaSignature2017/blob/master/LICENSE
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,6 +53,7 @@
 | 
				
			|||||||
		"@types/cbor": "5.0.0",
 | 
							"@types/cbor": "5.0.0",
 | 
				
			||||||
		"@types/dateformat": "3.0.1",
 | 
							"@types/dateformat": "3.0.1",
 | 
				
			||||||
		"@types/double-ended-queue": "2.1.1",
 | 
							"@types/double-ended-queue": "2.1.1",
 | 
				
			||||||
 | 
							"@types/escape-regexp": "0.0.0",
 | 
				
			||||||
		"@types/glob": "7.1.1",
 | 
							"@types/glob": "7.1.1",
 | 
				
			||||||
		"@types/gulp": "4.0.6",
 | 
							"@types/gulp": "4.0.6",
 | 
				
			||||||
		"@types/gulp-rename": "0.0.33",
 | 
							"@types/gulp-rename": "0.0.33",
 | 
				
			||||||
@@ -60,6 +61,7 @@
 | 
				
			|||||||
		"@types/is-url": "1.2.28",
 | 
							"@types/is-url": "1.2.28",
 | 
				
			||||||
		"@types/js-yaml": "3.12.3",
 | 
							"@types/js-yaml": "3.12.3",
 | 
				
			||||||
		"@types/jsdom": "16.2.1",
 | 
							"@types/jsdom": "16.2.1",
 | 
				
			||||||
 | 
							"@types/jsonld": "1.5.1",
 | 
				
			||||||
		"@types/katex": "0.11.0",
 | 
							"@types/katex": "0.11.0",
 | 
				
			||||||
		"@types/koa": "2.11.3",
 | 
							"@types/koa": "2.11.3",
 | 
				
			||||||
		"@types/koa-bodyparser": "4.3.0",
 | 
							"@types/koa-bodyparser": "4.3.0",
 | 
				
			||||||
@@ -126,6 +128,7 @@
 | 
				
			|||||||
		"dateformat": "3.0.3",
 | 
							"dateformat": "3.0.3",
 | 
				
			||||||
		"diskusage": "1.1.3",
 | 
							"diskusage": "1.1.3",
 | 
				
			||||||
		"double-ended-queue": "2.1.0-0",
 | 
							"double-ended-queue": "2.1.0-0",
 | 
				
			||||||
 | 
							"escape-regexp": "0.0.1",
 | 
				
			||||||
		"eslint": "6.8.0",
 | 
							"eslint": "6.8.0",
 | 
				
			||||||
		"eslint-plugin-vue": "6.2.2",
 | 
							"eslint-plugin-vue": "6.2.2",
 | 
				
			||||||
		"eventemitter3": "4.0.0",
 | 
							"eventemitter3": "4.0.0",
 | 
				
			||||||
@@ -156,6 +159,7 @@
 | 
				
			|||||||
		"jsdom": "16.2.2",
 | 
							"jsdom": "16.2.2",
 | 
				
			||||||
		"json5": "2.1.3",
 | 
							"json5": "2.1.3",
 | 
				
			||||||
		"json5-loader": "4.0.0",
 | 
							"json5-loader": "4.0.0",
 | 
				
			||||||
 | 
							"jsonld": "3.1.0",
 | 
				
			||||||
		"jsrsasign": "8.0.15",
 | 
							"jsrsasign": "8.0.15",
 | 
				
			||||||
		"katex": "0.11.1",
 | 
							"katex": "0.11.1",
 | 
				
			||||||
		"koa": "2.11.0",
 | 
							"koa": "2.11.0",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								src/@types/http-signature.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/@types/http-signature.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -23,6 +23,8 @@ declare module 'http-signature' {
 | 
				
			|||||||
		scheme: string;
 | 
							scheme: string;
 | 
				
			||||||
		params: ISignature;
 | 
							params: ISignature;
 | 
				
			||||||
		signingString: string;
 | 
							signingString: string;
 | 
				
			||||||
 | 
							algorithm: string;
 | 
				
			||||||
 | 
							keyId: string;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	type RequestSignerConstructorOptions =
 | 
						type RequestSignerConstructorOptions =
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ import procesObjectStorage from './processors/object-storage';
 | 
				
			|||||||
import { queueLogger } from './logger';
 | 
					import { queueLogger } from './logger';
 | 
				
			||||||
import { DriveFile } from '../models/entities/drive-file';
 | 
					import { DriveFile } from '../models/entities/drive-file';
 | 
				
			||||||
import { getJobInfo } from './get-job-info';
 | 
					import { getJobInfo } from './get-job-info';
 | 
				
			||||||
 | 
					import { IActivity } from '../remote/activitypub/type';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function initializeQueue(name: string, limitPerSec = -1) {
 | 
					function initializeQueue(name: string, limitPerSec = -1) {
 | 
				
			||||||
	return new Queue(name, {
 | 
						return new Queue(name, {
 | 
				
			||||||
@@ -29,6 +30,12 @@ function initializeQueue(name: string, limitPerSec = -1) {
 | 
				
			|||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type InboxJobData = {
 | 
				
			||||||
 | 
						activity: IActivity,
 | 
				
			||||||
 | 
						/** HTTP-Signature */
 | 
				
			||||||
 | 
						signature: httpSignature.IParsedSignature
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function renderError(e: Error): any {
 | 
					function renderError(e: Error): any {
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
		stack: e?.stack,
 | 
							stack: e?.stack,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,95 +1,111 @@
 | 
				
			|||||||
import * as Bull from 'bull';
 | 
					import * as Bull from 'bull';
 | 
				
			||||||
import * as httpSignature from 'http-signature';
 | 
					import * as httpSignature from 'http-signature';
 | 
				
			||||||
import { IRemoteUser } from '../../models/entities/user';
 | 
					 | 
				
			||||||
import perform from '../../remote/activitypub/perform';
 | 
					import perform from '../../remote/activitypub/perform';
 | 
				
			||||||
import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person';
 | 
					 | 
				
			||||||
import Logger from '../../services/logger';
 | 
					import Logger from '../../services/logger';
 | 
				
			||||||
import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc';
 | 
					import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc';
 | 
				
			||||||
import { Instances, Users, UserPublickeys } from '../../models';
 | 
					import { Instances } from '../../models';
 | 
				
			||||||
import { instanceChart } from '../../services/chart';
 | 
					import { instanceChart } from '../../services/chart';
 | 
				
			||||||
import { UserPublickey } from '../../models/entities/user-publickey';
 | 
					 | 
				
			||||||
import { fetchMeta } from '../../misc/fetch-meta';
 | 
					import { fetchMeta } from '../../misc/fetch-meta';
 | 
				
			||||||
import { toPuny } from '../../misc/convert-host';
 | 
					import { toPuny, extractDbHost } from '../../misc/convert-host';
 | 
				
			||||||
import { validActor } from '../../remote/activitypub/type';
 | 
					import { getApId } from '../../remote/activitypub/type';
 | 
				
			||||||
import { ensure } from '../../prelude/ensure';
 | 
					 | 
				
			||||||
import { fetchNodeinfo } from '../../services/fetch-nodeinfo';
 | 
					import { fetchNodeinfo } from '../../services/fetch-nodeinfo';
 | 
				
			||||||
 | 
					import { InboxJobData } from '..';
 | 
				
			||||||
 | 
					import DbResolver from '../../remote/activitypub/db-resolver';
 | 
				
			||||||
 | 
					import { resolvePerson } from '../../remote/activitypub/models/person';
 | 
				
			||||||
 | 
					import { LdSignature } from '../../remote/activitypub/misc/ld-signature';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logger = new Logger('inbox');
 | 
					const logger = new Logger('inbox');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ユーザーのinboxにアクティビティが届いた時の処理
 | 
					// ユーザーのinboxにアクティビティが届いた時の処理
 | 
				
			||||||
export default async (job: Bull.Job): Promise<void> => {
 | 
					export default async (job: Bull.Job<InboxJobData>): Promise<string> => {
 | 
				
			||||||
	const signature = job.data.signature;
 | 
						const signature = job.data.signature;	// HTTP-signature
 | 
				
			||||||
	const activity = job.data.activity;
 | 
						const activity = job.data.activity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//#region Log
 | 
						//#region Log
 | 
				
			||||||
	const info = Object.assign({}, activity);
 | 
						const info = Object.assign({}, activity);
 | 
				
			||||||
	delete info['@context'];
 | 
						delete info['@context'];
 | 
				
			||||||
	delete info['signature'];
 | 
					 | 
				
			||||||
	logger.debug(JSON.stringify(info, null, 2));
 | 
						logger.debug(JSON.stringify(info, null, 2));
 | 
				
			||||||
	//#endregion
 | 
						//#endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const keyIdLower = signature.keyId.toLowerCase();
 | 
					 | 
				
			||||||
	let user: IRemoteUser;
 | 
					 | 
				
			||||||
	let key: UserPublickey;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (keyIdLower.startsWith('acct:')) {
 | 
					 | 
				
			||||||
		logger.warn(`Old keyId is no longer supported. ${keyIdLower}`);
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// アクティビティ内のホストの検証
 | 
					 | 
				
			||||||
	const host = toPuny(new URL(signature.keyId).hostname);
 | 
						const host = toPuny(new URL(signature.keyId).hostname);
 | 
				
			||||||
	try {
 | 
					 | 
				
			||||||
		ValidateActivity(activity, host);
 | 
					 | 
				
			||||||
	} catch (e) {
 | 
					 | 
				
			||||||
		logger.warn(e.message);
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ブロックしてたら中断
 | 
						// ブロックしてたら中断
 | 
				
			||||||
	const meta = await fetchMeta();
 | 
						const meta = await fetchMeta();
 | 
				
			||||||
	if (meta.blockedHosts.includes(host)) {
 | 
						if (meta.blockedHosts.includes(host)) {
 | 
				
			||||||
		logger.info(`Blocked request: ${host}`);
 | 
							return `Blocked request: ${host}`;
 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const _key = await UserPublickeys.findOne({
 | 
						const keyIdLower = signature.keyId.toLowerCase();
 | 
				
			||||||
		keyId: signature.keyId
 | 
						if (keyIdLower.startsWith('acct:')) {
 | 
				
			||||||
	});
 | 
							return `Old keyId is no longer supported. ${keyIdLower}`;
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (_key) {
 | 
					 | 
				
			||||||
		// 登録済みユーザー
 | 
					 | 
				
			||||||
		user = await Users.findOne(_key.userId) as IRemoteUser;
 | 
					 | 
				
			||||||
		key = _key;
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		// 未登録ユーザーの場合はリモート解決
 | 
					 | 
				
			||||||
		user = await resolvePerson(activity.actor) as IRemoteUser;
 | 
					 | 
				
			||||||
		if (user == null) {
 | 
					 | 
				
			||||||
			throw new Error('failed to resolve user');
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		key = await UserPublickeys.findOne(user.id).then(ensure);
 | 
						const dbResolver = new DbResolver();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// HTTP-Signature keyIdを元にDBから取得
 | 
				
			||||||
 | 
						let authUser = await dbResolver.getAuthUserFromKeyId(signature.keyId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得
 | 
				
			||||||
 | 
						if (authUser == null) {
 | 
				
			||||||
 | 
							authUser = await dbResolver.getAuthUserFromApId(getApId(activity.actor));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
 | 
						// それでもわからなければ終了
 | 
				
			||||||
	if (activity.type === 'Update') {
 | 
						if (authUser == null) {
 | 
				
			||||||
		if (activity.object && validActor.includes(activity.object.type)) {
 | 
							return `skip: failed to resolve user`;
 | 
				
			||||||
			if (!httpSignature.verifySignature(signature, key.keyPem)) {
 | 
						}
 | 
				
			||||||
				logger.warn('Update activity received, but signature verification failed.');
 | 
					
 | 
				
			||||||
			} else {
 | 
						// HTTP-Signatureの検証
 | 
				
			||||||
				updatePerson(activity.actor, null, activity.object);
 | 
						if (!httpSignature.verifySignature(signature, authUser.key.keyPem)) {
 | 
				
			||||||
 | 
							return 'signature verification failed';
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// signatureのsignerは、activity.actorと一致する必要がある
 | 
				
			||||||
 | 
						if (authUser.user.uri !== activity.actor) {
 | 
				
			||||||
 | 
							// 一致しなくても、でも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);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// keyIdからLD-Signatureのユーザーを取得
 | 
				
			||||||
 | 
								authUser = await dbResolver.getAuthUserFromKeyId(activity.signature.creator);
 | 
				
			||||||
 | 
								if (authUser == null) {
 | 
				
			||||||
 | 
									return `skip: LD-Signatureのユーザーが取得できませんでした`;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// LD-Signature検証
 | 
				
			||||||
 | 
								const ldSignature = new LdSignature();
 | 
				
			||||||
 | 
								const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
 | 
				
			||||||
 | 
								if (!verified) {
 | 
				
			||||||
 | 
									return `skip: LD-Signatureの検証に失敗しました`;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// もう一度actorチェック
 | 
				
			||||||
 | 
								if (authUser.user.uri !== activity.actor) {
 | 
				
			||||||
 | 
									return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!httpSignature.verifySignature(signature, key.keyPem)) {
 | 
						// activity.idがあればホストが署名者のホストであることを確認する
 | 
				
			||||||
		logger.error('signature verification failed');
 | 
						if (typeof activity.id === 'string') {
 | 
				
			||||||
		return;
 | 
							const signerHost = extractDbHost(authUser.user.uri!);
 | 
				
			||||||
 | 
							const activityIdHost = extractDbHost(activity.id);
 | 
				
			||||||
 | 
							if (signerHost !== activityIdHost) {
 | 
				
			||||||
 | 
								return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update stats
 | 
						// Update stats
 | 
				
			||||||
	registerOrFetchInstanceDoc(user.host).then(i => {
 | 
						registerOrFetchInstanceDoc(authUser.user.host).then(i => {
 | 
				
			||||||
		Instances.update(i.id, {
 | 
							Instances.update(i.id, {
 | 
				
			||||||
			latestRequestReceivedAt: new Date(),
 | 
								latestRequestReceivedAt: new Date(),
 | 
				
			||||||
			lastCommunicatedAt: new Date(),
 | 
								lastCommunicatedAt: new Date(),
 | 
				
			||||||
@@ -102,42 +118,6 @@ export default async (job: Bull.Job): Promise<void> => {
 | 
				
			|||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// アクティビティを処理
 | 
						// アクティビティを処理
 | 
				
			||||||
	await perform(user, activity);
 | 
						await perform(authUser.user, activity);
 | 
				
			||||||
 | 
						return `ok`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Validate host in activity
 | 
					 | 
				
			||||||
 * @param activity Activity
 | 
					 | 
				
			||||||
 * @param host Expect host
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
function ValidateActivity(activity: any, host: string) {
 | 
					 | 
				
			||||||
	// id (if exists)
 | 
					 | 
				
			||||||
	if (typeof activity.id === 'string') {
 | 
					 | 
				
			||||||
		const uriHost = toPuny(new URL(activity.id).hostname);
 | 
					 | 
				
			||||||
		if (host !== uriHost) {
 | 
					 | 
				
			||||||
			const diag = activity.signature ? '. Has LD-Signature. Forwarded?' : '';
 | 
					 | 
				
			||||||
			throw new Error(`activity.id(${activity.id}) has different host(${host})${diag}`);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// actor (if exists)
 | 
					 | 
				
			||||||
	if (typeof activity.actor === 'string') {
 | 
					 | 
				
			||||||
		const uriHost = toPuny(new URL(activity.actor).hostname);
 | 
					 | 
				
			||||||
		if (host !== uriHost) throw new Error('activity.actor has different host');
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// For Create activity
 | 
					 | 
				
			||||||
	if (activity.type === 'Create' && activity.object) {
 | 
					 | 
				
			||||||
		// object.id (if exists)
 | 
					 | 
				
			||||||
		if (typeof activity.object.id === 'string') {
 | 
					 | 
				
			||||||
			const uriHost = toPuny(new URL(activity.object.id).hostname);
 | 
					 | 
				
			||||||
			if (host !== uriHost) throw new Error('activity.object.id has different host');
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// object.attributedTo (if exists)
 | 
					 | 
				
			||||||
		if (typeof activity.object.attributedTo === 'string') {
 | 
					 | 
				
			||||||
			const uriHost = toPuny(new URL(activity.object.attributedTo).hostname);
 | 
					 | 
				
			||||||
			if (host !== uriHost) throw new Error('activity.object.attributedTo has different host');
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										122
									
								
								src/remote/activitypub/db-resolver.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/remote/activitypub/db-resolver.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					import config from '../../config';
 | 
				
			||||||
 | 
					import { Note } from '../../models/entities/note';
 | 
				
			||||||
 | 
					import { User, IRemoteUser } from '../../models/entities/user';
 | 
				
			||||||
 | 
					import { UserPublickey } from '../../models/entities/user-publickey';
 | 
				
			||||||
 | 
					import { Notes, Users, UserPublickeys } from '../../models';
 | 
				
			||||||
 | 
					import { IObject, getApId } from './type';
 | 
				
			||||||
 | 
					import { resolvePerson } from './models/person';
 | 
				
			||||||
 | 
					import { ensure } from '../../prelude/ensure';
 | 
				
			||||||
 | 
					import escapeRegexp = require('escape-regexp');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class DbResolver {
 | 
				
			||||||
 | 
						constructor() {
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * AP Note => Misskey Note in DB
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public async getNoteFromApId(value: string | IObject): Promise<Note | null> {
 | 
				
			||||||
 | 
							const parsed = this.parseUri(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (parsed.id) {
 | 
				
			||||||
 | 
								return (await Notes.findOne({
 | 
				
			||||||
 | 
									id: parsed.id
 | 
				
			||||||
 | 
								})) || null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (parsed.uri) {
 | 
				
			||||||
 | 
								return (await Notes.findOne({
 | 
				
			||||||
 | 
									uri: parsed.uri
 | 
				
			||||||
 | 
								})) || null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * AP Person => Misskey User in DB
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public async getUserFromApId(value: string | IObject): Promise<User | null> {
 | 
				
			||||||
 | 
							const parsed = this.parseUri(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (parsed.id) {
 | 
				
			||||||
 | 
								return (await Users.findOne({
 | 
				
			||||||
 | 
									id: parsed.id
 | 
				
			||||||
 | 
								})) || null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (parsed.uri) {
 | 
				
			||||||
 | 
								return (await Users.findOne({
 | 
				
			||||||
 | 
									uri: parsed.uri
 | 
				
			||||||
 | 
								})) || null;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * AP KeyId => Misskey User and Key
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public async getAuthUserFromKeyId(keyId: string): Promise<AuthUser | null> {
 | 
				
			||||||
 | 
							const key = await UserPublickeys.findOne({
 | 
				
			||||||
 | 
								keyId
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (key == null) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const user = await Users.findOne(key.userId) as IRemoteUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								user,
 | 
				
			||||||
 | 
								key
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * AP Actor id => Misskey User and Key
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public async getAuthUserFromApId(uri: string): Promise<AuthUser | null> {
 | 
				
			||||||
 | 
							const user = await resolvePerson(uri) as IRemoteUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (user == null) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const key = await UserPublickeys.findOne(user.id).then(ensure);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								user,
 | 
				
			||||||
 | 
								key
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public parseUri(value: string | IObject): UriParseResult {
 | 
				
			||||||
 | 
							const uri = getApId(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/' + '(\\w+)' + '/' + '(\\w+)');
 | 
				
			||||||
 | 
							const matchLocal = uri.match(localRegex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (matchLocal) {
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									type: matchLocal[1],
 | 
				
			||||||
 | 
									id: matchLocal[2]
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									uri
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type AuthUser = {
 | 
				
			||||||
 | 
						user: IRemoteUser;
 | 
				
			||||||
 | 
						key: UserPublickey;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UriParseResult = {
 | 
				
			||||||
 | 
						/** id in DB (local object only) */
 | 
				
			||||||
 | 
						id?: string;
 | 
				
			||||||
 | 
						/** uri in DB (remote object only) */
 | 
				
			||||||
 | 
						uri?: string;
 | 
				
			||||||
 | 
						/** hint of type (local object only, ex: notes, users) */
 | 
				
			||||||
 | 
						type?: string
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@@ -1,28 +1,22 @@
 | 
				
			|||||||
import { IRemoteUser } from '../../../../models/entities/user';
 | 
					import { IRemoteUser } from '../../../../models/entities/user';
 | 
				
			||||||
import config from '../../../../config';
 | 
					 | 
				
			||||||
import accept from '../../../../services/following/requests/accept';
 | 
					import accept from '../../../../services/following/requests/accept';
 | 
				
			||||||
import { IFollow } from '../../type';
 | 
					import { IFollow } from '../../type';
 | 
				
			||||||
import { Users } from '../../../../models';
 | 
					import DbResolver from '../../db-resolver';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
 | 
					export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
 | 
				
			||||||
	const id = typeof activity.actor === 'string' ? activity.actor : activity.actor.id;
 | 
						// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
 | 
				
			||||||
	if (id == null) throw new Error('missing id');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!id.startsWith(config.url + '/')) {
 | 
						const dbResolver = new DbResolver();
 | 
				
			||||||
		return;
 | 
						const follower = await dbResolver.getUserFromApId(activity.actor);
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const follower = await Users.findOne({
 | 
					 | 
				
			||||||
		id: id.split('/').pop()
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (follower == null) {
 | 
						if (follower == null) {
 | 
				
			||||||
		throw new Error('follower not found');
 | 
							return `skip: follower not found`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (follower.host != null) {
 | 
						if (follower.host != null) {
 | 
				
			||||||
		throw new Error('フォローリクエストしたユーザーはローカルユーザーではありません');
 | 
							return `skip: follower is not a local user`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await accept(actor, follower);
 | 
						await accept(actor, follower);
 | 
				
			||||||
 | 
						return `ok`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,32 +1,22 @@
 | 
				
			|||||||
import config from '../../../../config';
 | 
					import { IBlock } from '../../type';
 | 
				
			||||||
import { IBlock, getApId } from '../../type';
 | 
					 | 
				
			||||||
import block from '../../../../services/blocking/create';
 | 
					import block from '../../../../services/blocking/create';
 | 
				
			||||||
import { apLogger } from '../../logger';
 | 
					 | 
				
			||||||
import { Users } from '../../../../models';
 | 
					 | 
				
			||||||
import { IRemoteUser } from '../../../../models/entities/user';
 | 
					import { IRemoteUser } from '../../../../models/entities/user';
 | 
				
			||||||
 | 
					import DbResolver from '../../db-resolver';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logger = apLogger;
 | 
					export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
 | 
				
			||||||
 | 
						// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
 | 
						const dbResolver = new DbResolver();
 | 
				
			||||||
	const id = getApId(activity.object);
 | 
						const blockee = await dbResolver.getUserFromApId(activity.object);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	const uri = getApId(activity);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	logger.info(`Block: ${uri}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (!id.startsWith(config.url + '/')) {
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const blockee = await Users.findOne(id.split('/').pop());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (blockee == null) {
 | 
						if (blockee == null) {
 | 
				
			||||||
		throw new Error('blockee not found');
 | 
							return `skip: blockee not found`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (blockee.host != null) {
 | 
						if (blockee.host != null) {
 | 
				
			||||||
		throw new Error('ブロックしようとしているユーザーはローカルユーザーではありません');
 | 
							return `skip: ブロックしようとしているユーザーはローカルユーザーではありません`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	block(actor, blockee);
 | 
						await block(actor, blockee);
 | 
				
			||||||
 | 
						return `ok`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,19 +3,39 @@ import { IRemoteUser } from '../../../../models/entities/user';
 | 
				
			|||||||
import { createNote, fetchNote } from '../../models/note';
 | 
					import { createNote, fetchNote } from '../../models/note';
 | 
				
			||||||
import { getApId, IObject, ICreate } from '../../type';
 | 
					import { getApId, IObject, ICreate } from '../../type';
 | 
				
			||||||
import { getApLock } from '../../../../misc/app-lock';
 | 
					import { getApLock } from '../../../../misc/app-lock';
 | 
				
			||||||
 | 
					import { extractDbHost } from '../../../../misc/convert-host';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 投稿作成アクティビティを捌きます
 | 
					 * 投稿作成アクティビティを捌きます
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<void> {
 | 
					export default async function(resolver: Resolver, actor: IRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
 | 
				
			||||||
	const uri = getApId(note);
 | 
						const uri = getApId(note);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (typeof note === 'object') {
 | 
				
			||||||
 | 
							if (actor.uri !== note.attributedTo) {
 | 
				
			||||||
 | 
								return `skip: actor.uri !== note.attributedTo`;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (typeof note.id === 'string') {
 | 
				
			||||||
 | 
								if (extractDbHost(actor.uri) !== extractDbHost(note.id)) {
 | 
				
			||||||
 | 
									return `skip: host in actor.uri !== note.id`;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const unlock = await getApLock(uri);
 | 
						const unlock = await getApLock(uri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try {
 | 
						try {
 | 
				
			||||||
		const exist = await fetchNote(note);
 | 
							const exist = await fetchNote(note);
 | 
				
			||||||
		if (exist == null) {
 | 
							if (exist) return 'skip: note exists';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		await createNote(note, resolver, silent);
 | 
							await createNote(note, resolver, silent);
 | 
				
			||||||
 | 
							return 'ok';
 | 
				
			||||||
 | 
						} catch (e) {
 | 
				
			||||||
 | 
							if (e.statusCode >= 400 && e.statusCode < 500) {
 | 
				
			||||||
 | 
								return `skip ${e.statusCode}`;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								throw e;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} finally {
 | 
						} finally {
 | 
				
			||||||
		unlock();
 | 
							unlock();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,22 +1,31 @@
 | 
				
			|||||||
import { IRemoteUser } from '../../../../models/entities/user';
 | 
					import { IRemoteUser } from '../../../../models/entities/user';
 | 
				
			||||||
import deleteNode from '../../../../services/note/delete';
 | 
					import deleteNode from '../../../../services/note/delete';
 | 
				
			||||||
import { apLogger } from '../../logger';
 | 
					import { apLogger } from '../../logger';
 | 
				
			||||||
import { Notes } from '../../../../models';
 | 
					import DbResolver from '../../db-resolver';
 | 
				
			||||||
 | 
					import { getApLock } from '../../../../misc/app-lock';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logger = apLogger;
 | 
					const logger = apLogger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async function(actor: IRemoteUser, uri: string): Promise<void> {
 | 
					export default async function(actor: IRemoteUser, uri: string): Promise<string> {
 | 
				
			||||||
	logger.info(`Deleting the Note: ${uri}`);
 | 
						logger.info(`Deleting the Note: ${uri}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const note = await Notes.findOne({ uri });
 | 
						const unlock = await getApLock(uri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							const dbResolver = new DbResolver();
 | 
				
			||||||
 | 
							const note = await dbResolver.getNoteFromApId(uri);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (note == null) {
 | 
							if (note == null) {
 | 
				
			||||||
		throw new Error('note not found');
 | 
								return 'note not found';
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (note.userId !== actor.id) {
 | 
							if (note.userId !== actor.id) {
 | 
				
			||||||
		throw new Error('投稿を削除しようとしているユーザーは投稿の作成者ではありません');
 | 
								return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		await deleteNode(actor, note);
 | 
							await deleteNode(actor, note);
 | 
				
			||||||
 | 
							return 'ok: deleted';
 | 
				
			||||||
 | 
						} finally {
 | 
				
			||||||
 | 
							unlock();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +1,20 @@
 | 
				
			|||||||
import { IRemoteUser } from '../../../models/entities/user';
 | 
					import { IRemoteUser } from '../../../models/entities/user';
 | 
				
			||||||
import config from '../../../config';
 | 
					 | 
				
			||||||
import follow from '../../../services/following/create';
 | 
					import follow from '../../../services/following/create';
 | 
				
			||||||
import { IFollow } from '../type';
 | 
					import { IFollow } from '../type';
 | 
				
			||||||
import { Users } from '../../../models';
 | 
					import DbResolver from '../db-resolver';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
 | 
					export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
 | 
				
			||||||
	const id = typeof activity.object === 'string' ? activity.object : activity.object.id;
 | 
						const dbResolver = new DbResolver();
 | 
				
			||||||
	if (id == null) throw new Error('missing id');
 | 
						const followee = await dbResolver.getUserFromApId(activity.object);
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (!id.startsWith(config.url + '/')) {
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const followee = await Users.findOne(id.split('/').pop());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (followee == null) {
 | 
						if (followee == null) {
 | 
				
			||||||
		throw new Error('followee not found');
 | 
							return `skip: followee not found`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (followee.host != null) {
 | 
						if (followee.host != null) {
 | 
				
			||||||
		throw new Error('フォローしようとしているユーザーはローカルユーザーではありません');
 | 
							return `skip: フォローしようとしているユーザーはローカルユーザーではありません`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await follow(actor, followee, activity.id);
 | 
						await follow(actor, followee, activity.id);
 | 
				
			||||||
 | 
						return `ok`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +1,22 @@
 | 
				
			|||||||
import { IRemoteUser } from '../../../../models/entities/user';
 | 
					import { IRemoteUser } from '../../../../models/entities/user';
 | 
				
			||||||
import config from '../../../../config';
 | 
					 | 
				
			||||||
import reject from '../../../../services/following/requests/reject';
 | 
					import reject from '../../../../services/following/requests/reject';
 | 
				
			||||||
import { IFollow } from '../../type';
 | 
					import { IFollow } from '../../type';
 | 
				
			||||||
import { Users } from '../../../../models';
 | 
					import DbResolver from '../../db-resolver';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
 | 
					export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
 | 
				
			||||||
	const id = typeof activity.actor === 'string' ? activity.actor : activity.actor.id;
 | 
						// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
 | 
				
			||||||
	if (id == null) throw new Error('missing id');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!id.startsWith(config.url + '/')) {
 | 
						const dbResolver = new DbResolver();
 | 
				
			||||||
		return;
 | 
						const follower = await dbResolver.getUserFromApId(activity.actor);
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const follower = await Users.findOne(id.split('/').pop());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (follower == null) {
 | 
						if (follower == null) {
 | 
				
			||||||
		throw new Error('follower not found');
 | 
							return `skip: follower not found`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (follower.host != null) {
 | 
						if (follower.host != null) {
 | 
				
			||||||
		throw new Error('フォローリクエストしたユーザーはローカルユーザーではありません');
 | 
							return `skip: follower is not a local user`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await reject(actor, follower);
 | 
						await reject(actor, follower);
 | 
				
			||||||
 | 
						return `ok`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,33 +1,20 @@
 | 
				
			|||||||
import config from '../../../../config';
 | 
					 | 
				
			||||||
import { IBlock } from '../../type';
 | 
					import { IBlock } from '../../type';
 | 
				
			||||||
import unblock from '../../../../services/blocking/delete';
 | 
					import unblock from '../../../../services/blocking/delete';
 | 
				
			||||||
import { apLogger } from '../../logger';
 | 
					 | 
				
			||||||
import { IRemoteUser } from '../../../../models/entities/user';
 | 
					import { IRemoteUser } from '../../../../models/entities/user';
 | 
				
			||||||
import { Users } from '../../../../models';
 | 
					import DbResolver from '../../db-resolver';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logger = apLogger;
 | 
					export default async (actor: IRemoteUser, activity: IBlock): Promise<string> => {
 | 
				
			||||||
 | 
						const dbResolver = new DbResolver();
 | 
				
			||||||
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
 | 
						const blockee = await dbResolver.getUserFromApId(activity.object);
 | 
				
			||||||
	const id = typeof activity.object === 'string' ? activity.object : activity.object.id;
 | 
					 | 
				
			||||||
	if (id == null) throw new Error('missing id');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const uri = activity.id || activity;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	logger.info(`UnBlock: ${uri}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (!id.startsWith(config.url + '/')) {
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const blockee = await Users.findOne(id.split('/').pop());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (blockee == null) {
 | 
						if (blockee == null) {
 | 
				
			||||||
		throw new Error('blockee not found');
 | 
							return `skip: blockee not found`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (blockee.host != null) {
 | 
						if (blockee.host != null) {
 | 
				
			||||||
		throw new Error('ブロック解除しようとしているユーザーはローカルユーザーではありません');
 | 
							return `skip: ブロック解除しようとしているユーザーはローカルユーザーではありません`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	unblock(actor, blockee);
 | 
						await unblock(actor, blockee);
 | 
				
			||||||
 | 
						return `ok`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +1,20 @@
 | 
				
			|||||||
import config from '../../../../config';
 | 
					 | 
				
			||||||
import unfollow from '../../../../services/following/delete';
 | 
					import unfollow from '../../../../services/following/delete';
 | 
				
			||||||
import cancelRequest from '../../../../services/following/requests/cancel';
 | 
					import cancelRequest from '../../../../services/following/requests/cancel';
 | 
				
			||||||
import { IFollow } from '../../type';
 | 
					import { IFollow } from '../../type';
 | 
				
			||||||
import { IRemoteUser } from '../../../../models/entities/user';
 | 
					import { IRemoteUser } from '../../../../models/entities/user';
 | 
				
			||||||
import { Users, FollowRequests, Followings } from '../../../../models';
 | 
					import { FollowRequests, Followings } from '../../../../models';
 | 
				
			||||||
 | 
					import DbResolver from '../../db-resolver';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
 | 
					export default async (actor: IRemoteUser, activity: IFollow): Promise<string> => {
 | 
				
			||||||
	const id = typeof activity.object === 'string' ? activity.object : activity.object.id;
 | 
						const dbResolver = new DbResolver();
 | 
				
			||||||
	if (id == null) throw new Error('missing id');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (!id.startsWith(config.url + '/')) {
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const followee = await Users.findOne(id.split('/').pop());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const followee = await dbResolver.getUserFromApId(activity.object);
 | 
				
			||||||
	if (followee == null) {
 | 
						if (followee == null) {
 | 
				
			||||||
		throw new Error('followee not found');
 | 
							return `skip: followee not found`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (followee.host != null) {
 | 
						if (followee.host != null) {
 | 
				
			||||||
		throw new Error('フォロー解除しようとしているユーザーはローカルユーザーではありません');
 | 
							return `skip: フォロー解除しようとしているユーザーはローカルユーザーではありません`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const req = await FollowRequests.findOne({
 | 
						const req = await FollowRequests.findOne({
 | 
				
			||||||
@@ -35,9 +29,13 @@ export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if (req) {
 | 
						if (req) {
 | 
				
			||||||
		await cancelRequest(followee, actor);
 | 
							await cancelRequest(followee, actor);
 | 
				
			||||||
 | 
							return `ok: follow request canceled`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (following) {
 | 
						if (following) {
 | 
				
			||||||
		await unfollow(actor, followee);
 | 
							await unfollow(actor, followee);
 | 
				
			||||||
 | 
							return `ok: unfollowed`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return `skip: リクエストもフォローもされていない`;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,28 +1,34 @@
 | 
				
			|||||||
import { IRemoteUser } from '../../../../models/entities/user';
 | 
					import { IRemoteUser } from '../../../../models/entities/user';
 | 
				
			||||||
import { IUpdate, IObject } from '../../type';
 | 
					import { IUpdate, validActor } from '../../type';
 | 
				
			||||||
import { apLogger } from '../../logger';
 | 
					import { apLogger } from '../../logger';
 | 
				
			||||||
import { updateQuestion } from '../../models/question';
 | 
					import { updateQuestion } from '../../models/question';
 | 
				
			||||||
 | 
					import Resolver from '../../resolver';
 | 
				
			||||||
 | 
					import { updatePerson } from '../../models/person';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Updateアクティビティを捌きます
 | 
					 * Updateアクティビティを捌きます
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export default async (actor: IRemoteUser, activity: IUpdate): Promise<void> => {
 | 
					export default async (actor: IRemoteUser, activity: IUpdate): Promise<string> => {
 | 
				
			||||||
	if ('actor' in activity && actor.uri !== activity.actor) {
 | 
						if ('actor' in activity && actor.uri !== activity.actor) {
 | 
				
			||||||
		throw new Error('invalid actor');
 | 
							return `skip: invalid actor`;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	apLogger.debug('Update');
 | 
						apLogger.debug('Update');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const object = activity.object as IObject;
 | 
						const resolver = new Resolver();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (object.type) {
 | 
						const object = await resolver.resolve(activity.object).catch(e => {
 | 
				
			||||||
		case 'Question':
 | 
							apLogger.error(`Resolution failed: ${e}`);
 | 
				
			||||||
			apLogger.debug('Question');
 | 
							throw e;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (validActor.includes(object.type)) {
 | 
				
			||||||
 | 
							await updatePerson(actor.uri!, resolver, object);
 | 
				
			||||||
 | 
							return `ok: Person updated`;
 | 
				
			||||||
 | 
						} else if (object.type === 'Question') {
 | 
				
			||||||
		await updateQuestion(object).catch(e => console.log(e));
 | 
							await updateQuestion(object).catch(e => console.log(e));
 | 
				
			||||||
			break;
 | 
							return `ok: Question updated`;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
		default:
 | 
							return `skip: Unknown type: ${object.type}`;
 | 
				
			||||||
			apLogger.warn(`Unknown type: ${object.type}`);
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										522
									
								
								src/remote/activitypub/misc/contexts.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										522
									
								
								src/remote/activitypub/misc/contexts.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,522 @@
 | 
				
			|||||||
 | 
					/* tslint:disable:quotemark indent */
 | 
				
			||||||
 | 
					const id_v1 = {
 | 
				
			||||||
 | 
					  "@context": {
 | 
				
			||||||
 | 
					    "id": "@id",
 | 
				
			||||||
 | 
					    "type": "@type",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "cred": "https://w3id.org/credentials#",
 | 
				
			||||||
 | 
					    "dc": "http://purl.org/dc/terms/",
 | 
				
			||||||
 | 
					    "identity": "https://w3id.org/identity#",
 | 
				
			||||||
 | 
					    "perm": "https://w3id.org/permissions#",
 | 
				
			||||||
 | 
					    "ps": "https://w3id.org/payswarm#",
 | 
				
			||||||
 | 
					    "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
 | 
				
			||||||
 | 
					    "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
 | 
				
			||||||
 | 
					    "sec": "https://w3id.org/security#",
 | 
				
			||||||
 | 
					    "schema": "http://schema.org/",
 | 
				
			||||||
 | 
					    "xsd": "http://www.w3.org/2001/XMLSchema#",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "Group": "https://www.w3.org/ns/activitystreams#Group",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "claim": {"@id": "cred:claim", "@type": "@id"},
 | 
				
			||||||
 | 
					    "credential": {"@id": "cred:credential", "@type": "@id"},
 | 
				
			||||||
 | 
					    "issued": {"@id": "cred:issued", "@type": "xsd:dateTime"},
 | 
				
			||||||
 | 
					    "issuer": {"@id": "cred:issuer", "@type": "@id"},
 | 
				
			||||||
 | 
					    "recipient": {"@id": "cred:recipient", "@type": "@id"},
 | 
				
			||||||
 | 
					    "Credential": "cred:Credential",
 | 
				
			||||||
 | 
					    "CryptographicKeyCredential": "cred:CryptographicKeyCredential",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "about": {"@id": "schema:about", "@type": "@id"},
 | 
				
			||||||
 | 
					    "address": {"@id": "schema:address", "@type": "@id"},
 | 
				
			||||||
 | 
					    "addressCountry": "schema:addressCountry",
 | 
				
			||||||
 | 
					    "addressLocality": "schema:addressLocality",
 | 
				
			||||||
 | 
					    "addressRegion": "schema:addressRegion",
 | 
				
			||||||
 | 
					    "comment": "rdfs:comment",
 | 
				
			||||||
 | 
					    "created": {"@id": "dc:created", "@type": "xsd:dateTime"},
 | 
				
			||||||
 | 
					    "creator": {"@id": "dc:creator", "@type": "@id"},
 | 
				
			||||||
 | 
					    "description": "schema:description",
 | 
				
			||||||
 | 
					    "email": "schema:email",
 | 
				
			||||||
 | 
					    "familyName": "schema:familyName",
 | 
				
			||||||
 | 
					    "givenName": "schema:givenName",
 | 
				
			||||||
 | 
					    "image": {"@id": "schema:image", "@type": "@id"},
 | 
				
			||||||
 | 
					    "label": "rdfs:label",
 | 
				
			||||||
 | 
					    "name": "schema:name",
 | 
				
			||||||
 | 
					    "postalCode": "schema:postalCode",
 | 
				
			||||||
 | 
					    "streetAddress": "schema:streetAddress",
 | 
				
			||||||
 | 
					    "title": "dc:title",
 | 
				
			||||||
 | 
					    "url": {"@id": "schema:url", "@type": "@id"},
 | 
				
			||||||
 | 
					    "Person": "schema:Person",
 | 
				
			||||||
 | 
					    "PostalAddress": "schema:PostalAddress",
 | 
				
			||||||
 | 
					    "Organization": "schema:Organization",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "identityService": {"@id": "identity:identityService", "@type": "@id"},
 | 
				
			||||||
 | 
					    "idp": {"@id": "identity:idp", "@type": "@id"},
 | 
				
			||||||
 | 
					    "Identity": "identity:Identity",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "paymentProcessor": "ps:processor",
 | 
				
			||||||
 | 
					    "preferences": {"@id": "ps:preferences", "@type": "@vocab"},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "cipherAlgorithm": "sec:cipherAlgorithm",
 | 
				
			||||||
 | 
					    "cipherData": "sec:cipherData",
 | 
				
			||||||
 | 
					    "cipherKey": "sec:cipherKey",
 | 
				
			||||||
 | 
					    "digestAlgorithm": "sec:digestAlgorithm",
 | 
				
			||||||
 | 
					    "digestValue": "sec:digestValue",
 | 
				
			||||||
 | 
					    "domain": "sec:domain",
 | 
				
			||||||
 | 
					    "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
 | 
				
			||||||
 | 
					    "initializationVector": "sec:initializationVector",
 | 
				
			||||||
 | 
					    "member": {"@id": "schema:member", "@type": "@id"},
 | 
				
			||||||
 | 
					    "memberOf": {"@id": "schema:memberOf", "@type": "@id"},
 | 
				
			||||||
 | 
					    "nonce": "sec:nonce",
 | 
				
			||||||
 | 
					    "normalizationAlgorithm": "sec:normalizationAlgorithm",
 | 
				
			||||||
 | 
					    "owner": {"@id": "sec:owner", "@type": "@id"},
 | 
				
			||||||
 | 
					    "password": "sec:password",
 | 
				
			||||||
 | 
					    "privateKey": {"@id": "sec:privateKey", "@type": "@id"},
 | 
				
			||||||
 | 
					    "privateKeyPem": "sec:privateKeyPem",
 | 
				
			||||||
 | 
					    "publicKey": {"@id": "sec:publicKey", "@type": "@id"},
 | 
				
			||||||
 | 
					    "publicKeyPem": "sec:publicKeyPem",
 | 
				
			||||||
 | 
					    "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"},
 | 
				
			||||||
 | 
					    "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"},
 | 
				
			||||||
 | 
					    "signature": "sec:signature",
 | 
				
			||||||
 | 
					    "signatureAlgorithm": "sec:signatureAlgorithm",
 | 
				
			||||||
 | 
					    "signatureValue": "sec:signatureValue",
 | 
				
			||||||
 | 
					    "CryptographicKey": "sec:Key",
 | 
				
			||||||
 | 
					    "EncryptedMessage": "sec:EncryptedMessage",
 | 
				
			||||||
 | 
					    "GraphSignature2012": "sec:GraphSignature2012",
 | 
				
			||||||
 | 
					    "LinkedDataSignature2015": "sec:LinkedDataSignature2015",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "accessControl": {"@id": "perm:accessControl", "@type": "@id"},
 | 
				
			||||||
 | 
					    "writePermission": {"@id": "perm:writePermission", "@type": "@id"}
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const security_v1 = {
 | 
				
			||||||
 | 
					  "@context": {
 | 
				
			||||||
 | 
					    "id": "@id",
 | 
				
			||||||
 | 
					    "type": "@type",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "dc": "http://purl.org/dc/terms/",
 | 
				
			||||||
 | 
					    "sec": "https://w3id.org/security#",
 | 
				
			||||||
 | 
					    "xsd": "http://www.w3.org/2001/XMLSchema#",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016",
 | 
				
			||||||
 | 
					    "Ed25519Signature2018": "sec:Ed25519Signature2018",
 | 
				
			||||||
 | 
					    "EncryptedMessage": "sec:EncryptedMessage",
 | 
				
			||||||
 | 
					    "GraphSignature2012": "sec:GraphSignature2012",
 | 
				
			||||||
 | 
					    "LinkedDataSignature2015": "sec:LinkedDataSignature2015",
 | 
				
			||||||
 | 
					    "LinkedDataSignature2016": "sec:LinkedDataSignature2016",
 | 
				
			||||||
 | 
					    "CryptographicKey": "sec:Key",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "authenticationTag": "sec:authenticationTag",
 | 
				
			||||||
 | 
					    "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm",
 | 
				
			||||||
 | 
					    "cipherAlgorithm": "sec:cipherAlgorithm",
 | 
				
			||||||
 | 
					    "cipherData": "sec:cipherData",
 | 
				
			||||||
 | 
					    "cipherKey": "sec:cipherKey",
 | 
				
			||||||
 | 
					    "created": {"@id": "dc:created", "@type": "xsd:dateTime"},
 | 
				
			||||||
 | 
					    "creator": {"@id": "dc:creator", "@type": "@id"},
 | 
				
			||||||
 | 
					    "digestAlgorithm": "sec:digestAlgorithm",
 | 
				
			||||||
 | 
					    "digestValue": "sec:digestValue",
 | 
				
			||||||
 | 
					    "domain": "sec:domain",
 | 
				
			||||||
 | 
					    "encryptionKey": "sec:encryptionKey",
 | 
				
			||||||
 | 
					    "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
 | 
				
			||||||
 | 
					    "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
 | 
				
			||||||
 | 
					    "initializationVector": "sec:initializationVector",
 | 
				
			||||||
 | 
					    "iterationCount": "sec:iterationCount",
 | 
				
			||||||
 | 
					    "nonce": "sec:nonce",
 | 
				
			||||||
 | 
					    "normalizationAlgorithm": "sec:normalizationAlgorithm",
 | 
				
			||||||
 | 
					    "owner": {"@id": "sec:owner", "@type": "@id"},
 | 
				
			||||||
 | 
					    "password": "sec:password",
 | 
				
			||||||
 | 
					    "privateKey": {"@id": "sec:privateKey", "@type": "@id"},
 | 
				
			||||||
 | 
					    "privateKeyPem": "sec:privateKeyPem",
 | 
				
			||||||
 | 
					    "publicKey": {"@id": "sec:publicKey", "@type": "@id"},
 | 
				
			||||||
 | 
					    "publicKeyBase58": "sec:publicKeyBase58",
 | 
				
			||||||
 | 
					    "publicKeyPem": "sec:publicKeyPem",
 | 
				
			||||||
 | 
					    "publicKeyWif": "sec:publicKeyWif",
 | 
				
			||||||
 | 
					    "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"},
 | 
				
			||||||
 | 
					    "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"},
 | 
				
			||||||
 | 
					    "salt": "sec:salt",
 | 
				
			||||||
 | 
					    "signature": "sec:signature",
 | 
				
			||||||
 | 
					    "signatureAlgorithm": "sec:signingAlgorithm",
 | 
				
			||||||
 | 
					    "signatureValue": "sec:signatureValue"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const activitystreams = {
 | 
				
			||||||
 | 
					  "@context": {
 | 
				
			||||||
 | 
					    "@vocab": "_:",
 | 
				
			||||||
 | 
					    "xsd": "http://www.w3.org/2001/XMLSchema#",
 | 
				
			||||||
 | 
					    "as": "https://www.w3.org/ns/activitystreams#",
 | 
				
			||||||
 | 
					    "ldp": "http://www.w3.org/ns/ldp#",
 | 
				
			||||||
 | 
					    "vcard": "http://www.w3.org/2006/vcard/ns#",
 | 
				
			||||||
 | 
					    "id": "@id",
 | 
				
			||||||
 | 
					    "type": "@type",
 | 
				
			||||||
 | 
					    "Accept": "as:Accept",
 | 
				
			||||||
 | 
					    "Activity": "as:Activity",
 | 
				
			||||||
 | 
					    "IntransitiveActivity": "as:IntransitiveActivity",
 | 
				
			||||||
 | 
					    "Add": "as:Add",
 | 
				
			||||||
 | 
					    "Announce": "as:Announce",
 | 
				
			||||||
 | 
					    "Application": "as:Application",
 | 
				
			||||||
 | 
					    "Arrive": "as:Arrive",
 | 
				
			||||||
 | 
					    "Article": "as:Article",
 | 
				
			||||||
 | 
					    "Audio": "as:Audio",
 | 
				
			||||||
 | 
					    "Block": "as:Block",
 | 
				
			||||||
 | 
					    "Collection": "as:Collection",
 | 
				
			||||||
 | 
					    "CollectionPage": "as:CollectionPage",
 | 
				
			||||||
 | 
					    "Relationship": "as:Relationship",
 | 
				
			||||||
 | 
					    "Create": "as:Create",
 | 
				
			||||||
 | 
					    "Delete": "as:Delete",
 | 
				
			||||||
 | 
					    "Dislike": "as:Dislike",
 | 
				
			||||||
 | 
					    "Document": "as:Document",
 | 
				
			||||||
 | 
					    "Event": "as:Event",
 | 
				
			||||||
 | 
					    "Follow": "as:Follow",
 | 
				
			||||||
 | 
					    "Flag": "as:Flag",
 | 
				
			||||||
 | 
					    "Group": "as:Group",
 | 
				
			||||||
 | 
					    "Ignore": "as:Ignore",
 | 
				
			||||||
 | 
					    "Image": "as:Image",
 | 
				
			||||||
 | 
					    "Invite": "as:Invite",
 | 
				
			||||||
 | 
					    "Join": "as:Join",
 | 
				
			||||||
 | 
					    "Leave": "as:Leave",
 | 
				
			||||||
 | 
					    "Like": "as:Like",
 | 
				
			||||||
 | 
					    "Link": "as:Link",
 | 
				
			||||||
 | 
					    "Mention": "as:Mention",
 | 
				
			||||||
 | 
					    "Note": "as:Note",
 | 
				
			||||||
 | 
					    "Object": "as:Object",
 | 
				
			||||||
 | 
					    "Offer": "as:Offer",
 | 
				
			||||||
 | 
					    "OrderedCollection": "as:OrderedCollection",
 | 
				
			||||||
 | 
					    "OrderedCollectionPage": "as:OrderedCollectionPage",
 | 
				
			||||||
 | 
					    "Organization": "as:Organization",
 | 
				
			||||||
 | 
					    "Page": "as:Page",
 | 
				
			||||||
 | 
					    "Person": "as:Person",
 | 
				
			||||||
 | 
					    "Place": "as:Place",
 | 
				
			||||||
 | 
					    "Profile": "as:Profile",
 | 
				
			||||||
 | 
					    "Question": "as:Question",
 | 
				
			||||||
 | 
					    "Reject": "as:Reject",
 | 
				
			||||||
 | 
					    "Remove": "as:Remove",
 | 
				
			||||||
 | 
					    "Service": "as:Service",
 | 
				
			||||||
 | 
					    "TentativeAccept": "as:TentativeAccept",
 | 
				
			||||||
 | 
					    "TentativeReject": "as:TentativeReject",
 | 
				
			||||||
 | 
					    "Tombstone": "as:Tombstone",
 | 
				
			||||||
 | 
					    "Undo": "as:Undo",
 | 
				
			||||||
 | 
					    "Update": "as:Update",
 | 
				
			||||||
 | 
					    "Video": "as:Video",
 | 
				
			||||||
 | 
					    "View": "as:View",
 | 
				
			||||||
 | 
					    "Listen": "as:Listen",
 | 
				
			||||||
 | 
					    "Read": "as:Read",
 | 
				
			||||||
 | 
					    "Move": "as:Move",
 | 
				
			||||||
 | 
					    "Travel": "as:Travel",
 | 
				
			||||||
 | 
					    "IsFollowing": "as:IsFollowing",
 | 
				
			||||||
 | 
					    "IsFollowedBy": "as:IsFollowedBy",
 | 
				
			||||||
 | 
					    "IsContact": "as:IsContact",
 | 
				
			||||||
 | 
					    "IsMember": "as:IsMember",
 | 
				
			||||||
 | 
					    "subject": {
 | 
				
			||||||
 | 
					      "@id": "as:subject",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "relationship": {
 | 
				
			||||||
 | 
					      "@id": "as:relationship",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "actor": {
 | 
				
			||||||
 | 
					      "@id": "as:actor",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "attributedTo": {
 | 
				
			||||||
 | 
					      "@id": "as:attributedTo",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "attachment": {
 | 
				
			||||||
 | 
					      "@id": "as:attachment",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "bcc": {
 | 
				
			||||||
 | 
					      "@id": "as:bcc",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "bto": {
 | 
				
			||||||
 | 
					      "@id": "as:bto",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "cc": {
 | 
				
			||||||
 | 
					      "@id": "as:cc",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "context": {
 | 
				
			||||||
 | 
					      "@id": "as:context",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "current": {
 | 
				
			||||||
 | 
					      "@id": "as:current",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "first": {
 | 
				
			||||||
 | 
					      "@id": "as:first",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "generator": {
 | 
				
			||||||
 | 
					      "@id": "as:generator",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "icon": {
 | 
				
			||||||
 | 
					      "@id": "as:icon",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "image": {
 | 
				
			||||||
 | 
					      "@id": "as:image",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "inReplyTo": {
 | 
				
			||||||
 | 
					      "@id": "as:inReplyTo",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "items": {
 | 
				
			||||||
 | 
					      "@id": "as:items",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "instrument": {
 | 
				
			||||||
 | 
					      "@id": "as:instrument",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "orderedItems": {
 | 
				
			||||||
 | 
					      "@id": "as:items",
 | 
				
			||||||
 | 
					      "@type": "@id",
 | 
				
			||||||
 | 
					      "@container": "@list"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "last": {
 | 
				
			||||||
 | 
					      "@id": "as:last",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "location": {
 | 
				
			||||||
 | 
					      "@id": "as:location",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "next": {
 | 
				
			||||||
 | 
					      "@id": "as:next",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "object": {
 | 
				
			||||||
 | 
					      "@id": "as:object",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "oneOf": {
 | 
				
			||||||
 | 
					      "@id": "as:oneOf",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "anyOf": {
 | 
				
			||||||
 | 
					      "@id": "as:anyOf",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "closed": {
 | 
				
			||||||
 | 
					      "@id": "as:closed",
 | 
				
			||||||
 | 
					      "@type": "xsd:dateTime"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "origin": {
 | 
				
			||||||
 | 
					      "@id": "as:origin",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "accuracy": {
 | 
				
			||||||
 | 
					      "@id": "as:accuracy",
 | 
				
			||||||
 | 
					      "@type": "xsd:float"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "prev": {
 | 
				
			||||||
 | 
					      "@id": "as:prev",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "preview": {
 | 
				
			||||||
 | 
					      "@id": "as:preview",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "replies": {
 | 
				
			||||||
 | 
					      "@id": "as:replies",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "result": {
 | 
				
			||||||
 | 
					      "@id": "as:result",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "audience": {
 | 
				
			||||||
 | 
					      "@id": "as:audience",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "partOf": {
 | 
				
			||||||
 | 
					      "@id": "as:partOf",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "tag": {
 | 
				
			||||||
 | 
					      "@id": "as:tag",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "target": {
 | 
				
			||||||
 | 
					      "@id": "as:target",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "to": {
 | 
				
			||||||
 | 
					      "@id": "as:to",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "url": {
 | 
				
			||||||
 | 
					      "@id": "as:url",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "altitude": {
 | 
				
			||||||
 | 
					      "@id": "as:altitude",
 | 
				
			||||||
 | 
					      "@type": "xsd:float"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "content": "as:content",
 | 
				
			||||||
 | 
					    "contentMap": {
 | 
				
			||||||
 | 
					      "@id": "as:content",
 | 
				
			||||||
 | 
					      "@container": "@language"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "name": "as:name",
 | 
				
			||||||
 | 
					    "nameMap": {
 | 
				
			||||||
 | 
					      "@id": "as:name",
 | 
				
			||||||
 | 
					      "@container": "@language"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "duration": {
 | 
				
			||||||
 | 
					      "@id": "as:duration",
 | 
				
			||||||
 | 
					      "@type": "xsd:duration"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "endTime": {
 | 
				
			||||||
 | 
					      "@id": "as:endTime",
 | 
				
			||||||
 | 
					      "@type": "xsd:dateTime"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "height": {
 | 
				
			||||||
 | 
					      "@id": "as:height",
 | 
				
			||||||
 | 
					      "@type": "xsd:nonNegativeInteger"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "href": {
 | 
				
			||||||
 | 
					      "@id": "as:href",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "hreflang": "as:hreflang",
 | 
				
			||||||
 | 
					    "latitude": {
 | 
				
			||||||
 | 
					      "@id": "as:latitude",
 | 
				
			||||||
 | 
					      "@type": "xsd:float"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "longitude": {
 | 
				
			||||||
 | 
					      "@id": "as:longitude",
 | 
				
			||||||
 | 
					      "@type": "xsd:float"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "mediaType": "as:mediaType",
 | 
				
			||||||
 | 
					    "published": {
 | 
				
			||||||
 | 
					      "@id": "as:published",
 | 
				
			||||||
 | 
					      "@type": "xsd:dateTime"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "radius": {
 | 
				
			||||||
 | 
					      "@id": "as:radius",
 | 
				
			||||||
 | 
					      "@type": "xsd:float"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "rel": "as:rel",
 | 
				
			||||||
 | 
					    "startIndex": {
 | 
				
			||||||
 | 
					      "@id": "as:startIndex",
 | 
				
			||||||
 | 
					      "@type": "xsd:nonNegativeInteger"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "startTime": {
 | 
				
			||||||
 | 
					      "@id": "as:startTime",
 | 
				
			||||||
 | 
					      "@type": "xsd:dateTime"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "summary": "as:summary",
 | 
				
			||||||
 | 
					    "summaryMap": {
 | 
				
			||||||
 | 
					      "@id": "as:summary",
 | 
				
			||||||
 | 
					      "@container": "@language"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "totalItems": {
 | 
				
			||||||
 | 
					      "@id": "as:totalItems",
 | 
				
			||||||
 | 
					      "@type": "xsd:nonNegativeInteger"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "units": "as:units",
 | 
				
			||||||
 | 
					    "updated": {
 | 
				
			||||||
 | 
					      "@id": "as:updated",
 | 
				
			||||||
 | 
					      "@type": "xsd:dateTime"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "width": {
 | 
				
			||||||
 | 
					      "@id": "as:width",
 | 
				
			||||||
 | 
					      "@type": "xsd:nonNegativeInteger"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "describes": {
 | 
				
			||||||
 | 
					      "@id": "as:describes",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "formerType": {
 | 
				
			||||||
 | 
					      "@id": "as:formerType",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "deleted": {
 | 
				
			||||||
 | 
					      "@id": "as:deleted",
 | 
				
			||||||
 | 
					      "@type": "xsd:dateTime"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "inbox": {
 | 
				
			||||||
 | 
					      "@id": "ldp:inbox",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "outbox": {
 | 
				
			||||||
 | 
					      "@id": "as:outbox",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "following": {
 | 
				
			||||||
 | 
					      "@id": "as:following",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "followers": {
 | 
				
			||||||
 | 
					      "@id": "as:followers",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "streams": {
 | 
				
			||||||
 | 
					      "@id": "as:streams",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "preferredUsername": "as:preferredUsername",
 | 
				
			||||||
 | 
					    "endpoints": {
 | 
				
			||||||
 | 
					      "@id": "as:endpoints",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "uploadMedia": {
 | 
				
			||||||
 | 
					      "@id": "as:uploadMedia",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "proxyUrl": {
 | 
				
			||||||
 | 
					      "@id": "as:proxyUrl",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "liked": {
 | 
				
			||||||
 | 
					      "@id": "as:liked",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "oauthAuthorizationEndpoint": {
 | 
				
			||||||
 | 
					      "@id": "as:oauthAuthorizationEndpoint",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "oauthTokenEndpoint": {
 | 
				
			||||||
 | 
					      "@id": "as:oauthTokenEndpoint",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "provideClientKey": {
 | 
				
			||||||
 | 
					      "@id": "as:provideClientKey",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "signClientKey": {
 | 
				
			||||||
 | 
					      "@id": "as:signClientKey",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "sharedInbox": {
 | 
				
			||||||
 | 
					      "@id": "as:sharedInbox",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Public": {
 | 
				
			||||||
 | 
					      "@id": "as:Public",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "source": "as:source",
 | 
				
			||||||
 | 
					    "likes": {
 | 
				
			||||||
 | 
					      "@id": "as:likes",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "shares": {
 | 
				
			||||||
 | 
					      "@id": "as:shares",
 | 
				
			||||||
 | 
					      "@type": "@id"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const CONTEXTS: Record<string, any> = {
 | 
				
			||||||
 | 
					  "https://w3id.org/identity/v1": id_v1,
 | 
				
			||||||
 | 
					  "https://w3id.org/security/v1": security_v1,
 | 
				
			||||||
 | 
					  "https://www.w3.org/ns/activitystreams": activitystreams,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										133
									
								
								src/remote/activitypub/misc/ld-signature.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/remote/activitypub/misc/ld-signature.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,133 @@
 | 
				
			|||||||
 | 
					import * as crypto from 'crypto';
 | 
				
			||||||
 | 
					import * as jsonld from 'jsonld';
 | 
				
			||||||
 | 
					import { CONTEXTS } from './contexts';
 | 
				
			||||||
 | 
					import fetch from 'node-fetch';
 | 
				
			||||||
 | 
					import { httpAgent, httpsAgent } from '../../../misc/fetch';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RsaSignature2017 based from https://github.com/transmute-industries/RsaSignature2017
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class LdSignature {
 | 
				
			||||||
 | 
						public debug = false;
 | 
				
			||||||
 | 
						public preLoad = true;
 | 
				
			||||||
 | 
						public loderTimeout = 10 * 1000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor() {
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public async signRsaSignature2017(data: any, privateKey: string, creator: string, domain?: string, created?: Date): Promise<any> {
 | 
				
			||||||
 | 
							const options = {
 | 
				
			||||||
 | 
								type: 'RsaSignature2017',
 | 
				
			||||||
 | 
								creator,
 | 
				
			||||||
 | 
								domain,
 | 
				
			||||||
 | 
								nonce: crypto.randomBytes(16).toString('hex'),
 | 
				
			||||||
 | 
								created: (created || new Date()).toISOString()
 | 
				
			||||||
 | 
							} as {
 | 
				
			||||||
 | 
								type: string;
 | 
				
			||||||
 | 
								creator: string;
 | 
				
			||||||
 | 
								domain: string;
 | 
				
			||||||
 | 
								nonce: string;
 | 
				
			||||||
 | 
								created: string;
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!domain) {
 | 
				
			||||||
 | 
								delete options.domain;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const toBeSigned = await this.createVerifyData(data, options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const signer = crypto.createSign('sha256');
 | 
				
			||||||
 | 
							signer.update(toBeSigned);
 | 
				
			||||||
 | 
							signer.end();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const signature = signer.sign(privateKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								...data,
 | 
				
			||||||
 | 
								signature: {
 | 
				
			||||||
 | 
									...options,
 | 
				
			||||||
 | 
									signatureValue: signature.toString('base64')
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public async verifyRsaSignature2017(data: any, publicKey: string): Promise<boolean> {
 | 
				
			||||||
 | 
							const toBeSigned = await this.createVerifyData(data, data.signature);
 | 
				
			||||||
 | 
							const verifier = crypto.createVerify('sha256');
 | 
				
			||||||
 | 
							verifier.update(toBeSigned);
 | 
				
			||||||
 | 
							return verifier.verify(publicKey, data.signature.signatureValue, 'base64');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public async createVerifyData(data: any, options: any) {
 | 
				
			||||||
 | 
							const transformedOptions = {
 | 
				
			||||||
 | 
								...options,
 | 
				
			||||||
 | 
								'@context': 'https://w3id.org/identity/v1'
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							delete transformedOptions['type'];
 | 
				
			||||||
 | 
							delete transformedOptions['id'];
 | 
				
			||||||
 | 
							delete transformedOptions['signatureValue'];
 | 
				
			||||||
 | 
							const canonizedOptions = await this.normalize(transformedOptions);
 | 
				
			||||||
 | 
							const optionsHash = this.sha256(canonizedOptions);
 | 
				
			||||||
 | 
							const transformedData = { ...data };
 | 
				
			||||||
 | 
							delete transformedData['signature'];
 | 
				
			||||||
 | 
							const cannonidedData = await this.normalize(transformedData);
 | 
				
			||||||
 | 
							const documentHash = this.sha256(cannonidedData);
 | 
				
			||||||
 | 
							const verifyData = `${optionsHash}${documentHash}`;
 | 
				
			||||||
 | 
							return verifyData;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public async normalize(data: any) {
 | 
				
			||||||
 | 
							const customLoader = this.getLoader();
 | 
				
			||||||
 | 
							return await jsonld.normalize(data, {
 | 
				
			||||||
 | 
								documentLoader: customLoader
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private getLoader() {
 | 
				
			||||||
 | 
							return async (url: string): Promise<any> => {
 | 
				
			||||||
 | 
								if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (this.preLoad) {
 | 
				
			||||||
 | 
									if (url in CONTEXTS) {
 | 
				
			||||||
 | 
										if (this.debug) console.debug(`HIT: ${url}`);
 | 
				
			||||||
 | 
										return {
 | 
				
			||||||
 | 
											contextUrl: null,
 | 
				
			||||||
 | 
											document: CONTEXTS[url],
 | 
				
			||||||
 | 
											documentUrl: url
 | 
				
			||||||
 | 
										};
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (this.debug) console.debug(`MISS: ${url}`);
 | 
				
			||||||
 | 
								const document = await this.fetchDocument(url);
 | 
				
			||||||
 | 
								return {
 | 
				
			||||||
 | 
									contextUrl: null,
 | 
				
			||||||
 | 
									document: document,
 | 
				
			||||||
 | 
									documentUrl: url
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private async fetchDocument(url: string) {
 | 
				
			||||||
 | 
							const json = await fetch(url, {
 | 
				
			||||||
 | 
								headers: {
 | 
				
			||||||
 | 
									Accept: 'application/ld+json, application/json',
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								timeout: this.loderTimeout,
 | 
				
			||||||
 | 
								agent: u => u.protocol == 'http:' ? httpAgent : httpsAgent,
 | 
				
			||||||
 | 
							}).then(res => {
 | 
				
			||||||
 | 
								if (!res.ok) {
 | 
				
			||||||
 | 
									throw `${res.status} ${res.statusText}`;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return res.json();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return json;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public sha256(data: string): string {
 | 
				
			||||||
 | 
							const hash = crypto.createHash('sha256');
 | 
				
			||||||
 | 
							hash.update(data);
 | 
				
			||||||
 | 
							return hash.digest('hex');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,7 +15,7 @@ import { apLogger } from '../logger';
 | 
				
			|||||||
import { DriveFile } from '../../../models/entities/drive-file';
 | 
					import { DriveFile } from '../../../models/entities/drive-file';
 | 
				
			||||||
import { deliverQuestionUpdate } from '../../../services/note/polls/update';
 | 
					import { deliverQuestionUpdate } from '../../../services/note/polls/update';
 | 
				
			||||||
import { extractDbHost, toPuny } from '../../../misc/convert-host';
 | 
					import { extractDbHost, toPuny } from '../../../misc/convert-host';
 | 
				
			||||||
import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
 | 
					import { Emojis, Polls, MessagingMessages } from '../../../models';
 | 
				
			||||||
import { Note } from '../../../models/entities/note';
 | 
					import { Note } from '../../../models/entities/note';
 | 
				
			||||||
import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji } from '../type';
 | 
					import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji } from '../type';
 | 
				
			||||||
import { Emoji } from '../../../models/entities/emoji';
 | 
					import { Emoji } from '../../../models/entities/emoji';
 | 
				
			||||||
@@ -26,6 +26,7 @@ import { getApLock } from '../../../misc/app-lock';
 | 
				
			|||||||
import { createMessage } from '../../../services/messages/create';
 | 
					import { createMessage } from '../../../services/messages/create';
 | 
				
			||||||
import { parseAudience } from '../audience';
 | 
					import { parseAudience } from '../audience';
 | 
				
			||||||
import { extractApMentions } from './mention';
 | 
					import { extractApMentions } from './mention';
 | 
				
			||||||
 | 
					import DbResolver from '../db-resolver';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const logger = apLogger;
 | 
					const logger = apLogger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,24 +57,9 @@ export function validateNote(object: any, uri: string) {
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 * Misskeyに対象のNoteが登録されていればそれを返します。
 | 
					 * Misskeyに対象のNoteが登録されていればそれを返します。
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> {
 | 
					export async function fetchNote(object: string | IObject): Promise<Note | null> {
 | 
				
			||||||
	const uri = getApId(value);
 | 
						const dbResolver = new DbResolver();
 | 
				
			||||||
 | 
						return await dbResolver.getNoteFromApId(object);
 | 
				
			||||||
	// URIがこのサーバーを指しているならデータベースからフェッチ
 | 
					 | 
				
			||||||
	if (uri.startsWith(config.url + '/')) {
 | 
					 | 
				
			||||||
		const id = uri.split('/').pop();
 | 
					 | 
				
			||||||
		return await Notes.findOne(id).then(x => x || null);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	//#region このサーバーに既に登録されていたらそれを返す
 | 
					 | 
				
			||||||
	const exist = await Notes.findOne({ uri });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (exist) {
 | 
					 | 
				
			||||||
		return exist;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	//#endregion
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return null;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,6 +67,15 @@ export interface IActivity extends IObject {
 | 
				
			|||||||
	actor: IObject | string;
 | 
						actor: IObject | string;
 | 
				
			||||||
	object: IObject | string;
 | 
						object: IObject | string;
 | 
				
			||||||
	target?: IObject | string;
 | 
						target?: IObject | string;
 | 
				
			||||||
 | 
						/** LD-Signature */
 | 
				
			||||||
 | 
						signature?: {
 | 
				
			||||||
 | 
							type: string;
 | 
				
			||||||
 | 
							created: Date;
 | 
				
			||||||
 | 
							creator: string;
 | 
				
			||||||
 | 
							domain?: string;
 | 
				
			||||||
 | 
							nonce?: string;
 | 
				
			||||||
 | 
							signatureValue: string;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface ICollection extends IObject {
 | 
					export interface ICollection extends IObject {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										56
									
								
								yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										56
									
								
								yarn.lock
									
									
									
									
									
								
							@@ -293,6 +293,11 @@
 | 
				
			|||||||
  resolved "https://registry.yarnpkg.com/@types/double-ended-queue/-/double-ended-queue-2.1.1.tgz#f077386134f0f736d927812c85c43a04f21ddc27"
 | 
					  resolved "https://registry.yarnpkg.com/@types/double-ended-queue/-/double-ended-queue-2.1.1.tgz#f077386134f0f736d927812c85c43a04f21ddc27"
 | 
				
			||||||
  integrity sha512-O2+umEIlHBVyi+ePmucPjpINqTvSnsz+hAok0D4IpvrOsIsDr6c34B0AbNXW2UDVYuxbv51z5dxnrRt23ohgWg==
 | 
					  integrity sha512-O2+umEIlHBVyi+ePmucPjpINqTvSnsz+hAok0D4IpvrOsIsDr6c34B0AbNXW2UDVYuxbv51z5dxnrRt23ohgWg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/escape-regexp@0.0.0":
 | 
				
			||||||
 | 
					  version "0.0.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.0.tgz#bff0225f9ef30d0dbdbe0e2a24283ee5342990c3"
 | 
				
			||||||
 | 
					  integrity sha512-HTansGo4tJ7K7W9I9LBdQqnHtPB/Y7tlS+EMrkboaAQLsRPhRpHaqAHe01K1HVXM5e1u1IplRd8EBh+pJrp7Dg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@types/eslint-visitor-keys@^1.0.0":
 | 
					"@types/eslint-visitor-keys@^1.0.0":
 | 
				
			||||||
  version "1.0.0"
 | 
					  version "1.0.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
 | 
					  resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
 | 
				
			||||||
@@ -414,6 +419,11 @@
 | 
				
			|||||||
  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
 | 
					  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339"
 | 
				
			||||||
  integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
 | 
					  integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"@types/jsonld@1.5.1":
 | 
				
			||||||
 | 
					  version "1.5.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.1.tgz#361e98bdc07814f5c98a42b4063430b243a8fa9b"
 | 
				
			||||||
 | 
					  integrity sha512-8XI88iiCBVqmNCMBqPOgJhJPPuiIW1Tp2sXqe3NwD137ljhQVkDWY8cuYBBDZQoBYfGzUJvja527bbwqVbRnHQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@types/katex@0.11.0":
 | 
					"@types/katex@0.11.0":
 | 
				
			||||||
  version "0.11.0"
 | 
					  version "0.11.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.0.tgz#b16c54ee670925ffef0616beae9e90c557e17334"
 | 
					  resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.0.tgz#b16c54ee670925ffef0616beae9e90c557e17334"
 | 
				
			||||||
@@ -1867,6 +1877,11 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001043:
 | 
				
			|||||||
  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e"
 | 
					  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e"
 | 
				
			||||||
  integrity sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg==
 | 
					  integrity sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					canonicalize@^1.0.1:
 | 
				
			||||||
 | 
					  version "1.0.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/canonicalize/-/canonicalize-1.0.1.tgz#657b4f3fa38a6ecb97a9e5b7b26d7a19cc6e0da9"
 | 
				
			||||||
 | 
					  integrity sha512-N3cmB3QLhS5TJ5smKFf1w42rJXWe6C1qP01z4dxJiI5v269buii4fLHWETDyf7yEd0azGLNC63VxNMiPd2u0Cg==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
caseless@~0.12.0:
 | 
					caseless@~0.12.0:
 | 
				
			||||||
  version "0.12.0"
 | 
					  version "0.12.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
 | 
					  resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
 | 
				
			||||||
@@ -5178,6 +5193,19 @@ json5@^1.0.1:
 | 
				
			|||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    minimist "^1.2.0"
 | 
					    minimist "^1.2.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jsonld@3.1.0:
 | 
				
			||||||
 | 
					  version "3.1.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-3.1.0.tgz#826a7a598942a3969d41301388c51b812a73c6d0"
 | 
				
			||||||
 | 
					  integrity sha512-9x/AbUsXMMZBPxGy98Y8qMz7CU3WCq1n0KcNfR1P4RZml5oZiEQM+53/VtStOHUTUyC6fX9Sml5olUOZRARTZw==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    canonicalize "^1.0.1"
 | 
				
			||||||
 | 
					    lru-cache "^5.1.1"
 | 
				
			||||||
 | 
					    object.fromentries "^2.0.2"
 | 
				
			||||||
 | 
					    rdf-canonize "^1.0.2"
 | 
				
			||||||
 | 
					    request "^2.88.0"
 | 
				
			||||||
 | 
					    semver "^6.3.0"
 | 
				
			||||||
 | 
					    xmldom "0.1.19"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jsprim@^1.2.2:
 | 
					jsprim@^1.2.2:
 | 
				
			||||||
  version "1.4.1"
 | 
					  version "1.4.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
 | 
					  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
 | 
				
			||||||
@@ -6258,6 +6286,11 @@ node-fetch@2.6.0:
 | 
				
			|||||||
  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
 | 
					  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
 | 
				
			||||||
  integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
 | 
					  integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					node-forge@^0.9.1:
 | 
				
			||||||
 | 
					  version "0.9.1"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5"
 | 
				
			||||||
 | 
					  integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node-object-hash@^1.2.0:
 | 
					node-object-hash@^1.2.0:
 | 
				
			||||||
  version "1.4.2"
 | 
					  version "1.4.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94"
 | 
					  resolved "https://registry.yarnpkg.com/node-object-hash/-/node-object-hash-1.4.2.tgz#385833d85b229902b75826224f6077be969a9e94"
 | 
				
			||||||
@@ -6435,6 +6468,16 @@ object.defaults@^1.0.0, object.defaults@^1.1.0:
 | 
				
			|||||||
    for-own "^1.0.0"
 | 
					    for-own "^1.0.0"
 | 
				
			||||||
    isobject "^3.0.0"
 | 
					    isobject "^3.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object.fromentries@^2.0.2:
 | 
				
			||||||
 | 
					  version "2.0.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9"
 | 
				
			||||||
 | 
					  integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    define-properties "^1.1.3"
 | 
				
			||||||
 | 
					    es-abstract "^1.17.0-next.1"
 | 
				
			||||||
 | 
					    function-bind "^1.1.1"
 | 
				
			||||||
 | 
					    has "^1.0.3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0:
 | 
					object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.0:
 | 
				
			||||||
  version "2.1.0"
 | 
					  version "2.1.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
 | 
					  resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
 | 
				
			||||||
@@ -7690,6 +7733,14 @@ rc@^1.2.7:
 | 
				
			|||||||
    minimist "^1.2.0"
 | 
					    minimist "^1.2.0"
 | 
				
			||||||
    strip-json-comments "~2.0.1"
 | 
					    strip-json-comments "~2.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					rdf-canonize@^1.0.2:
 | 
				
			||||||
 | 
					  version "1.1.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/rdf-canonize/-/rdf-canonize-1.1.0.tgz#61d1609bbdb3234b8f38c9c34ad889bf670e089d"
 | 
				
			||||||
 | 
					  integrity sha512-DV06OnhVfl2zcZJQCt+YvU+hoZVgpyQpNFLeAmghq8RJybUxD3B4LRzlBquYS5k+LLd8/c3g5Gnhkqjw5qRMvg==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    node-forge "^0.9.1"
 | 
				
			||||||
 | 
					    semver "^6.3.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
read-pkg-up@^1.0.1:
 | 
					read-pkg-up@^1.0.1:
 | 
				
			||||||
  version "1.0.1"
 | 
					  version "1.0.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
 | 
					  resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
 | 
				
			||||||
@@ -10225,6 +10276,11 @@ xmlchars@^2.2.0:
 | 
				
			|||||||
  resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
 | 
					  resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
 | 
				
			||||||
  integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
 | 
					  integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					xmldom@0.1.19:
 | 
				
			||||||
 | 
					  version "0.1.19"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
 | 
				
			||||||
 | 
					  integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
 | 
					xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1:
 | 
				
			||||||
  version "4.0.2"
 | 
					  version "4.0.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
 | 
					  resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user