forkbomb DOS mitigation
This commit is contained in:
parent
a2a1636c10
commit
58a3a0b7d4
4 changed files with 22 additions and 17 deletions
|
@ -10,7 +10,7 @@ import { updatePerson } from '../../models/person.js';
|
||||||
*/
|
*/
|
||||||
export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<string> => {
|
export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<string> => {
|
||||||
if ('actor' in activity && actor.uri !== activity.actor) {
|
if ('actor' in activity && actor.uri !== activity.actor) {
|
||||||
return `skip: invalid actor`;
|
return 'skip: invalid actor';
|
||||||
}
|
}
|
||||||
|
|
||||||
apLogger.debug('Update');
|
apLogger.debug('Update');
|
||||||
|
@ -24,10 +24,10 @@ export default async (actor: CacheableRemoteUser, activity: IUpdate): Promise<st
|
||||||
|
|
||||||
if (isActor(object)) {
|
if (isActor(object)) {
|
||||||
await updatePerson(actor.uri!, resolver, object);
|
await updatePerson(actor.uri!, resolver, object);
|
||||||
return `ok: Person updated`;
|
return 'ok: Person updated';
|
||||||
} else if (getApType(object) === 'Question') {
|
} else if (getApType(object) === 'Question') {
|
||||||
await updateQuestion(object).catch(e => console.log(e));
|
await updateQuestion(object, resolver).catch(e => console.log(e));
|
||||||
return `ok: Question updated`;
|
return 'ok: Question updated';
|
||||||
} else {
|
} else {
|
||||||
return `skip: Unknown type: ${getApType(object)}`;
|
return `skip: Unknown type: ${getApType(object)}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -271,7 +271,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
||||||
});
|
});
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
await updateFeatured(user!.id).catch(err => logger.error(err));
|
await updateFeatured(user!.id, resolver).catch(err => logger.error(err));
|
||||||
|
|
||||||
return user!;
|
return user!;
|
||||||
}
|
}
|
||||||
|
@ -384,7 +384,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
|
||||||
followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
followerSharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||||
});
|
});
|
||||||
|
|
||||||
await updateFeatured(exist.id).catch(err => logger.error(err));
|
await updateFeatured(exist.id, resolver).catch(err => logger.error(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -462,14 +462,14 @@ export function analyzeAttachments(attachments: IObject | IObject[] | undefined)
|
||||||
return { fields, services };
|
return { fields, services };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateFeatured(userId: User['id']) {
|
export async function updateFeatured(userId: User['id'], resolver?: Resolver) {
|
||||||
const user = await Users.findOneByOrFail({ id: userId });
|
const user = await Users.findOneByOrFail({ id: userId });
|
||||||
if (!Users.isRemoteUser(user)) return;
|
if (!Users.isRemoteUser(user)) return;
|
||||||
if (!user.featured) return;
|
if (!user.featured) return;
|
||||||
|
|
||||||
logger.info(`Updating the featured: ${user.uri}`);
|
logger.info(`Updating the featured: ${user.uri}`);
|
||||||
|
|
||||||
const resolver = new Resolver();
|
if (resolver == null) resolver = new Resolver();
|
||||||
|
|
||||||
// Resolve to (Ordered)Collection Object
|
// Resolve to (Ordered)Collection Object
|
||||||
const collection = await resolver.resolveCollection(user.featured);
|
const collection = await resolver.resolveCollection(user.featured);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import config from '@/config/index.js';
|
import config from '@/config/index.js';
|
||||||
|
import { Notes, Polls } from '@/models/index.js';
|
||||||
|
import { IPoll } from '@/models/entities/poll.js';
|
||||||
import Resolver from '../resolver.js';
|
import Resolver from '../resolver.js';
|
||||||
import { IObject, IQuestion, isQuestion } from '../type.js';
|
import { IObject, IQuestion, isQuestion } from '../type.js';
|
||||||
import { apLogger } from '../logger.js';
|
import { apLogger } from '../logger.js';
|
||||||
import { Notes, Polls } from '@/models/index.js';
|
|
||||||
import { IPoll } from '@/models/entities/poll.js';
|
|
||||||
|
|
||||||
export async function extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise<IPoll> {
|
export async function extractPollFromQuestion(source: string | IObject, resolver?: Resolver): Promise<IPoll> {
|
||||||
if (resolver == null) resolver = new Resolver();
|
if (resolver == null) resolver = new Resolver();
|
||||||
|
@ -40,7 +40,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver
|
||||||
* @param uri URI of AP Question object
|
* @param uri URI of AP Question object
|
||||||
* @returns true if updated
|
* @returns true if updated
|
||||||
*/
|
*/
|
||||||
export async function updateQuestion(value: any) {
|
export async function updateQuestion(value: any, resolver?: Resolver) {
|
||||||
const uri = typeof value === 'string' ? value : value.id;
|
const uri = typeof value === 'string' ? value : value.id;
|
||||||
|
|
||||||
// URIがこのサーバーを指しているならスキップ
|
// URIがこのサーバーを指しているならスキップ
|
||||||
|
@ -55,7 +55,7 @@ export async function updateQuestion(value: any) {
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// resolve new Question object
|
// resolve new Question object
|
||||||
const resolver = new Resolver();
|
if (resolver == null) resolver = new Resolver();
|
||||||
const question = await resolver.resolve(value) as IQuestion;
|
const question = await resolver.resolve(value) as IQuestion;
|
||||||
apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
|
apLogger.debug(`fetched question: ${JSON.stringify(question, null, 2)}`);
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,7 @@ import { ILocalUser } from '@/models/entities/user.js';
|
||||||
import { getInstanceActor } from '@/services/instance-actor.js';
|
import { getInstanceActor } from '@/services/instance-actor.js';
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { extractDbHost, isSelfHost } from '@/misc/convert-host.js';
|
import { extractDbHost, isSelfHost } from '@/misc/convert-host.js';
|
||||||
import { signedGet } from './request.js';
|
|
||||||
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
|
|
||||||
import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js';
|
import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js';
|
||||||
import { parseUri } from './db-resolver.js';
|
|
||||||
import renderNote from '@/remote/activitypub/renderer/note.js';
|
import renderNote from '@/remote/activitypub/renderer/note.js';
|
||||||
import { renderLike } from '@/remote/activitypub/renderer/like.js';
|
import { renderLike } from '@/remote/activitypub/renderer/like.js';
|
||||||
import { renderPerson } from '@/remote/activitypub/renderer/person.js';
|
import { renderPerson } from '@/remote/activitypub/renderer/person.js';
|
||||||
|
@ -15,12 +12,16 @@ import renderQuestion from '@/remote/activitypub/renderer/question.js';
|
||||||
import renderCreate from '@/remote/activitypub/renderer/create.js';
|
import renderCreate from '@/remote/activitypub/renderer/create.js';
|
||||||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||||
import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
||||||
|
import { parseUri } from './db-resolver.js';
|
||||||
|
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
|
||||||
|
import { signedGet } from './request.js';
|
||||||
|
|
||||||
export default class Resolver {
|
export default class Resolver {
|
||||||
private history: Set<string>;
|
private history: Set<string>;
|
||||||
private user?: ILocalUser;
|
private user?: ILocalUser;
|
||||||
|
private recursionLimit?: number;
|
||||||
|
|
||||||
constructor() {
|
constructor(recursionLimit = 100) {
|
||||||
this.history = new Set();
|
this.history = new Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +61,10 @@ export default class Resolver {
|
||||||
throw new Error('cannot resolve already resolved one');
|
throw new Error('cannot resolve already resolved one');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.recursionLimit && this.history.size > this.recursionLimit) {
|
||||||
|
throw new Error('hit recursion limit');
|
||||||
|
}
|
||||||
|
|
||||||
this.history.add(value);
|
this.history.add(value);
|
||||||
|
|
||||||
const host = extractDbHost(value);
|
const host = extractDbHost(value);
|
||||||
|
@ -123,7 +128,7 @@ export default class Resolver {
|
||||||
if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI');
|
if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI');
|
||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
[parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id }))
|
[parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id })),
|
||||||
)
|
)
|
||||||
.then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url)));
|
.then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url)));
|
||||||
default:
|
default:
|
||||||
|
|
Loading…
Reference in a new issue