diff --git a/src/define_config.ts b/src/define_config.ts index 890c123..da15d05 100644 --- a/src/define_config.ts +++ b/src/define_config.ts @@ -11,10 +11,16 @@ import proxyAddr from 'proxy-addr' import string from '@poppinss/utils/string' import type { ServerConfig } from './types/server.js' +type UserDefinedServerConfig = Partial< + Omit & { + trustProxy: ((address: string, distance: number) => boolean) | boolean | string + } +> + /** * Define configuration for the HTTP server */ -export function defineConfig(config: Partial): ServerConfig { +export function defineConfig(config: UserDefinedServerConfig): ServerConfig { const normalizedConfig = { allowMethodSpoofing: false, trustProxy: proxyAddr.compile('loopback'), @@ -52,5 +58,13 @@ export function defineConfig(config: Partial): ServerConfig { normalizedConfig.cookie.maxAge = string.seconds.parse(normalizedConfig.cookie.maxAge) } + if (typeof normalizedConfig.trustProxy === 'boolean') { + const tpValue = normalizedConfig.trustProxy + normalizedConfig.trustProxy = (_, __) => tpValue + } else if (typeof normalizedConfig.trustProxy === 'string') { + const tpValue = normalizedConfig.trustProxy + normalizedConfig.trustProxy = proxyAddr.compile(tpValue) + } + return normalizedConfig } diff --git a/tests/define_config.spec.ts b/tests/define_config.spec.ts index 378b2fb..05ede1c 100644 --- a/tests/define_config.spec.ts +++ b/tests/define_config.spec.ts @@ -46,4 +46,16 @@ test.group('Define config', () => { useAsyncLocalStorage: false, }) }) + + test('compile trustProxy config when boolean', ({ assert }) => { + const config = defineConfig({ trustProxy: true }) + + assert.typeOf(config.trustProxy, 'function') + }) + + test('comfile trustProxy config when string', ({ assert }) => { + const config = defineConfig({ trustProxy: 'loopback' }) + + assert.typeOf(config.trustProxy, 'function') + }) }) diff --git a/tests/request.spec.ts b/tests/request.spec.ts index e6b92ef..9b257f9 100644 --- a/tests/request.spec.ts +++ b/tests/request.spec.ts @@ -9,6 +9,7 @@ import pem from 'pem' import supertest from 'supertest' +import proxyAddr from 'proxy-addr' import { test } from '@japa/runner' import Middleware from '@poppinss/middleware' import { createServer as httpsServer } from 'node:https' @@ -517,6 +518,90 @@ test.group('Request', () => { }) }) + test('do not trust proxy when trustProxy does not allow it', async ({ assert }) => { + const { url } = await httpServer.create((req, res) => { + req.headers['x-forwarded-for'] = '10.10.10.10' + const request = new RequestFactory() + .merge({ + req, + res, + encryption, + config: { + trustProxy: proxyAddr.compile('192.168.1.0/24'), + }, + }) + .create() + res.writeHead(200, { 'content-type': 'application/json' }) + res.end(JSON.stringify({ ip: request.ip() })) + }) + + const { body } = await supertest(url).get('/') + assert.notEqual(body.ip, '10.10.10.10') + }) + + test('trust proxy when trustProxy allows it', async ({ assert }) => { + const { url } = await httpServer.create((req, res) => { + req.headers['x-forwarded-for'] = '10.10.10.10' + const request = new RequestFactory() + .merge({ + req, + res, + encryption, + config: { + trustProxy: proxyAddr.compile('loopback'), + }, + }) + .create() + res.writeHead(200, { 'content-type': 'application/json' }) + res.end(JSON.stringify({ ip: request.ip() })) + }) + + const { body } = await supertest(url).get('/') + assert.equal(body.ip, '10.10.10.10') + }) + + test('trust all proxies when trustProxy is true', async ({ assert }) => { + const { url } = await httpServer.create((req, res) => { + req.headers['x-forwarded-for'] = '10.10.10.10' + const request = new RequestFactory() + .merge({ + req, + res, + encryption, + config: { + trustProxy: (_, __) => true, + }, + }) + .create() + res.writeHead(200, { 'content-type': 'application/json' }) + res.end(JSON.stringify({ ip: request.ip() })) + }) + + const { body } = await supertest(url).get('/') + assert.equal(body.ip, '10.10.10.10') + }) + + test('trust no proxy when trustProxy is false', async ({ assert }) => { + const { url } = await httpServer.create((req, res) => { + req.headers['x-forwarded-for'] = '10.10.10.10' + const request = new RequestFactory() + .merge({ + req, + res, + encryption, + config: { + trustProxy: (_, __) => false, + }, + }) + .create() + res.writeHead(200, { 'content-type': 'application/json' }) + res.end(JSON.stringify({ ip: request.ip() })) + }) + + const { body } = await supertest(url).get('/') + assert.notEqual(body.ip, '10.10.10.10') + }) + test('return request url without query string', async ({ assert }) => { const { url } = await httpServer.create((req, res) => { const request = new RequestFactory().merge({ req, res, encryption }).create()