Implement trusted cloudflare ips

Co-authored-by: William Oldham <github@binaryoverload.co.uk>
This commit is contained in:
mrjvs 2023-12-21 20:38:51 +01:00
parent 07ecd445f9
commit 59ab9b48bd
5 changed files with 40 additions and 3 deletions

View File

@ -12,6 +12,8 @@ services:
POSTGRES_PASSWORD: postgres
volumes:
- 'postgres_data:/var/lib/postgresql/data'
redis:
image: redis
# custom services
backend:

View File

@ -4,4 +4,8 @@ export const dockerFragment: FragmentSchema = {
postgres: {
connection: 'postgres://postgres:postgres@postgres:5432/postgres',
},
ratelimits: {
enabled: true,
redisUrl: 'redis://redis:6379',
},
};

View File

@ -16,6 +16,9 @@ export const configSchema = z.object({
// should it trust reverse proxy headers? (for ip gathering)
trustProxy: z.coerce.boolean().default(false),
// should it trust cloudflare headers? (for ip gathering, cloudflare has priority)
trustCloudflare: z.coerce.boolean().default(false),
// prefix for where the instance is run on. for example set it to /backend if you're hosting it on example.com/backend
// if this is set, do not apply url rewriting before proxing
basePath: z.string().default('/'),

View File

@ -2,6 +2,7 @@ import Redis from 'ioredis';
import RateLimiter from 'async-ratelimiter';
import ms from 'ms';
import { StatusError } from '@/services/error';
import { IpReq, getIp } from '@/services/ip';
export interface LimiterOptions {
redis: Redis;
@ -26,8 +27,8 @@ export class Limiter {
this.redis = ops.redis;
}
async bump(req: { ip: string }, ops: BucketOptions) {
const ip = req.ip;
async bump(req: IpReq, ops: BucketOptions) {
const ip = getIp(req);
if (!this.buckets[ops.id]) {
this.buckets[ops.id] = {
limiter: new RateLimiter({
@ -54,7 +55,7 @@ export class Limiter {
};
}
async assertAndBump(req: { ip: string }, ops: BucketOptions) {
async assertAndBump(req: IpReq, ops: BucketOptions) {
const { hasBeenLimited } = await this.bump(req, ops);
if (hasBeenLimited) {
throw new StatusError('Ratelimited', 429);

27
src/services/ip.ts Normal file
View File

@ -0,0 +1,27 @@
import { conf } from '@/config';
import { IncomingHttpHeaders } from 'http';
export type IpReq = {
ip: string;
headers: IncomingHttpHeaders;
};
const trustCloudflare = conf.server.trustCloudflare;
function getSingleHeader(
headers: IncomingHttpHeaders,
key: string,
): string | undefined {
const header = headers[key];
if (Array.isArray(header)) return header[0];
return header;
}
export function getIp(req: IpReq) {
const cfIp = getSingleHeader(req.headers, 'cf-connecting-ip');
if (trustCloudflare && cfIp) {
return cfIp;
}
return req.ip;
}