Fix IP address rate limit (#8758)
* Fix IP address rate limit * CHANGELOG * Tune getIpHash
This commit is contained in:
		
							
								
								
									
										9
									
								
								packages/backend/src/misc/get-ip-hash.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								packages/backend/src/misc/get-ip-hash.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
import IPCIDR from 'ip-cidr';
 | 
			
		||||
 | 
			
		||||
export function getIpHash(ip: string) {
 | 
			
		||||
	// because a single person may control many IPv6 addresses,
 | 
			
		||||
	// only a /64 subnet prefix of any IP will be taken into account.
 | 
			
		||||
	// (this means for IPv4 the entire address is used)
 | 
			
		||||
	const prefix = IPCIDR.createAddress(ip).mask(64);
 | 
			
		||||
	return 'ip-' + BigInt('0b' + prefix).toString(36);
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@ import endpoints, { IEndpointMeta } from './endpoints.js';
 | 
			
		||||
import { ApiError } from './error.js';
 | 
			
		||||
import { apiLogger } from './logger.js';
 | 
			
		||||
import { AccessToken } from '@/models/entities/access-token.js';
 | 
			
		||||
import IPCIDR from 'ip-cidr';
 | 
			
		||||
import { getIpHash } from '@/misc/get-ip-hash.js';
 | 
			
		||||
 | 
			
		||||
const accessDenied = {
 | 
			
		||||
	message: 'Access denied.',
 | 
			
		||||
@@ -33,18 +33,13 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 | 
			
		||||
		throw new ApiError(accessDenied);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ep.meta.requireCredential && ep.meta.limit && !isModerator) {
 | 
			
		||||
	if (ep.meta.limit && !isModerator) {
 | 
			
		||||
		// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
 | 
			
		||||
		let limitActor: string;
 | 
			
		||||
		if (user) {
 | 
			
		||||
			limitActor = user.id;
 | 
			
		||||
		} else {
 | 
			
		||||
			// because a single person may control many IPv6 addresses,
 | 
			
		||||
			// only a /64 subnet prefix of any IP will be taken into account.
 | 
			
		||||
			// (this means for IPv4 the entire address is used)
 | 
			
		||||
			const ip = IPCIDR.createAddress(ctx.ip).mask(64);
 | 
			
		||||
 | 
			
		||||
			limitActor = 'ip-' + parseInt(ip, 2).toString(36);
 | 
			
		||||
			limitActor = getIpHash(ctx!.ip);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const limit = Object.assign({}, ep.meta.limit);
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import { verifyLogin, hash } from '../2fa.js';
 | 
			
		||||
import { randomBytes } from 'node:crypto';
 | 
			
		||||
import { IsNull } from 'typeorm';
 | 
			
		||||
import { limiter } from '../limiter.js';
 | 
			
		||||
import { getIpHash } from '@/misc/get-ip-hash.js';
 | 
			
		||||
 | 
			
		||||
export default async (ctx: Koa.Context) => {
 | 
			
		||||
	ctx.set('Access-Control-Allow-Origin', config.url);
 | 
			
		||||
@@ -27,7 +28,7 @@ export default async (ctx: Koa.Context) => {
 | 
			
		||||
 | 
			
		||||
	try {
 | 
			
		||||
		// not more than 1 attempt per second and not more than 10 attempts per hour
 | 
			
		||||
		await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, ctx.ip);
 | 
			
		||||
		await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip));
 | 
			
		||||
	} catch (err) {
 | 
			
		||||
		ctx.status = 429;
 | 
			
		||||
		ctx.body = {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user