mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
core: improved RateLimiter
This commit is contained in:
5 files changed
+23
-22
No files matched your search
@@ -12,10 +12,10 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.662.0",
|
||||
"@aws-sdk/lib-storage": "3.662.0",
|
||||
"@aws-sdk/s3-presigned-post": "3.662.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.662.0",
|
||||
"@aws-sdk/client-s3": "3.664.0",
|
||||
"@aws-sdk/lib-storage": "3.664.0",
|
||||
"@aws-sdk/s3-presigned-post": "3.664.0",
|
||||
"@aws-sdk/s3-request-presigner": "3.664.0",
|
||||
"@graphql-tools/schema": "^10.0.6",
|
||||
"@hydrooj/framework": "workspace:^",
|
||||
"@hydrooj/utils": "workspace:^",
|
||||
@@ -58,7 +58,7 @@
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/koa-static-cache": "^5.1.4",
|
||||
"@types/lodash": "^4.17.9",
|
||||
"@types/lodash": "^4.17.10",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/mongodb-uri": "^0.9.4",
|
||||
"@types/nodemailer": "^6.4.16",
|
||||
|
||||
@@ -59,7 +59,7 @@ const getLoader = (type: LoadTask, filename: string, dontLoad = false) => async
|
||||
|
||||
export const handler = getLoader('handler', 'handler', true);
|
||||
export const addon = getLoader('addon', 'index');
|
||||
export const model = getLoader('model', 'model', true);
|
||||
export const model = getLoader('model', 'model');
|
||||
export const lib = getLoader('lib', 'lib', true);
|
||||
export const script = getLoader('script', 'script', true);
|
||||
export const service = getLoader('service', 'service');
|
||||
|
||||
@@ -481,8 +481,8 @@ export class ProblemSubmitHandler extends ProblemDetailHandler {
|
||||
throw new ProblemNotAllowPretestError('type');
|
||||
}
|
||||
}
|
||||
await this.limitRate('add_record', 60, system.get('limit.submission_user'), true);
|
||||
await this.limitRate('add_record', 60, system.get('limit.submission'), false);
|
||||
await this.limitRate('add_record', 60, system.get('limit.submission_user'), '{{user}}');
|
||||
await this.limitRate('add_record', 60, system.get('limit.submission'));
|
||||
const files: Record<string, string> = {};
|
||||
if (!code) {
|
||||
const file = this.request.files?.file;
|
||||
@@ -554,8 +554,8 @@ export class ProblemHackHandler extends ProblemDetailHandler {
|
||||
@param('autoOrganizeInput', Types.Boolean, true)
|
||||
@param('tid', Types.ObjectId, true)
|
||||
async post(domainId: string, input = '', autoOrganizeInput = false, tid?: ObjectId) {
|
||||
await this.limitRate('add_record', 60, system.get('limit.submission_user'), true);
|
||||
await this.limitRate('add_record', 60, system.get('limit.submission'), false);
|
||||
await this.limitRate('add_record', 60, system.get('limit.submission_user'), '{{user}}');
|
||||
await this.limitRate('add_record', 60, system.get('limit.submission'));
|
||||
const id = `${this.user._id}/${nanoid()}`;
|
||||
if (this.request.files?.file?.size > 0) {
|
||||
const file = this.request.files.file;
|
||||
|
||||
@@ -58,8 +58,8 @@ class UserLoginHandler extends Handler {
|
||||
}
|
||||
}
|
||||
await Promise.all([
|
||||
this.limitRate('user_login', 60, 30, false),
|
||||
this.limitRate(`user_login_${uname}`, 60, 5, false),
|
||||
this.limitRate('user_login', 60, 30),
|
||||
this.limitRate('user_login_id', 60, 5, uname),
|
||||
oplog.log(this, 'user.login', { redirect }),
|
||||
]);
|
||||
if (udoc.tfa || udoc.authn) {
|
||||
@@ -99,7 +99,7 @@ class UserSudoHandler extends Handler {
|
||||
async post(domainId: string, password = '', tfa = '', authnChallenge = '') {
|
||||
if (!this.session.sudoArgs?.method) throw new ForbiddenError();
|
||||
await Promise.all([
|
||||
this.limitRate('user_sudo', 60, 5, true),
|
||||
this.limitRate('user_sudo', 60, 5, '{{user}}'),
|
||||
oplog.log(this, 'user.sudo', {}),
|
||||
]);
|
||||
if (this.user.authn && authnChallenge) {
|
||||
@@ -206,8 +206,8 @@ export class UserRegisterHandler extends Handler {
|
||||
const mailDomain = mail.split('@')[1];
|
||||
if (await BlackListModel.get(`mail::${mailDomain}`)) throw new BlacklistedError(mailDomain);
|
||||
await Promise.all([
|
||||
this.limitRate(`send_mail_${mail}`, 60, 3, false),
|
||||
this.limitRate('send_mail', 3600, 30, false),
|
||||
this.limitRate('send_mail', 60, 1, mail),
|
||||
this.limitRate('send_mail', 3600, 30),
|
||||
oplog.log(this, 'user.register', {}),
|
||||
]);
|
||||
const t = await token.add(
|
||||
@@ -239,7 +239,7 @@ class UserRegisterWithCodeHandler extends Handler {
|
||||
this.tdoc = await token.get(code, token.TYPE_REGISTRATION);
|
||||
if (!this.tdoc || (!this.tdoc.mail && !this.tdoc.phone)) {
|
||||
// prevent brute forcing tokens
|
||||
await this.limitRate('user_register_with_code', 60, 5, false);
|
||||
await this.limitRate('user_register_with_code', 60, 5);
|
||||
throw new InvalidTokenError(token.TYPE_TEXTS[token.TYPE_REGISTRATION], code);
|
||||
}
|
||||
}
|
||||
@@ -295,8 +295,8 @@ class UserLostPassHandler extends Handler {
|
||||
const udoc = await user.getByEmail('system', mail);
|
||||
if (!udoc) throw new UserNotFoundError(mail);
|
||||
await Promise.all([
|
||||
this.limitRate('send_mail', 3600, 30, false),
|
||||
this.limitRate(`user_lostpass_${mail}`, 60, 3, false),
|
||||
this.limitRate('send_mail', 3600, 30),
|
||||
this.limitRate('send_mail', 60, 1, mail),
|
||||
oplog.log(this, 'user.lostpass', {}),
|
||||
]);
|
||||
const [tid] = await token.add(
|
||||
|
||||
@@ -33,7 +33,7 @@ declare module '@hydrooj/framework' {
|
||||
|
||||
paginate<T>(cursor: FindCursor<T>, page: number, key: string): Promise<[docs: T[], numPages: number, count: number]>;
|
||||
progress(message: string, params: any[]): void;
|
||||
limitRate(op: string, periodSecs: number, maxOperations: number, withUserId?: boolean): Promise<void>;
|
||||
limitRate(op: string, periodSecs: number, maxOperations: number, defaultKey?: string): Promise<void>;
|
||||
renderTitle(str: string): string;
|
||||
}
|
||||
}
|
||||
@@ -253,14 +253,15 @@ export async function apply(ctx: Context) {
|
||||
Hydro.model.message.sendInfo(this.user._id, JSON.stringify({ message, params }));
|
||||
},
|
||||
async limitRate(
|
||||
op: string, periodSecs: number, maxOperations: number, withUserId = system.get('limit.by_user'),
|
||||
op: string, periodSecs: number, maxOperations: number, defaultKey = system.get('limit.by_user') ? '{{ip}}@{{user}}' : '{{ip}}',
|
||||
) {
|
||||
if (ignoredLimit.includes(op)) return;
|
||||
if (this.user && this.user.hasPriv(PRIV.PRIV_UNLIMITED_ACCESS)) return;
|
||||
const overrideLimit = system.get(`limit.${op}`);
|
||||
if (overrideLimit) maxOperations = overrideLimit;
|
||||
let id = this.request.ip;
|
||||
if (withUserId) id += `@${this.user._id}`;
|
||||
// deprecated: remove boolean support in future
|
||||
if (typeof defaultKey === 'boolean') defaultKey = defaultKey ? '{{user}}' : '{{ip}}';
|
||||
const id = defaultKey.replace('{{ip}}', this.request.ip).replace('{{user}}', this.user._id);
|
||||
await opcount.inc(op, id, periodSecs, maxOperations);
|
||||
},
|
||||
renderTitle(str: string) {
|
||||
|
||||
Reference in new issue
Block a user