Implement trusted cloudflare ips
Co-authored-by: William Oldham <github@binaryoverload.co.uk>
This commit is contained in:
parent
07ecd445f9
commit
59ab9b48bd
|
@ -12,6 +12,8 @@ services:
|
|||
POSTGRES_PASSWORD: postgres
|
||||
volumes:
|
||||
- 'postgres_data:/var/lib/postgresql/data'
|
||||
redis:
|
||||
image: redis
|
||||
|
||||
# custom services
|
||||
backend:
|
||||
|
|
|
@ -4,4 +4,8 @@ export const dockerFragment: FragmentSchema = {
|
|||
postgres: {
|
||||
connection: 'postgres://postgres:postgres@postgres:5432/postgres',
|
||||
},
|
||||
ratelimits: {
|
||||
enabled: true,
|
||||
redisUrl: 'redis://redis:6379',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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('/'),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue