Aktueller Stand

This commit is contained in:
2026-01-22 19:05:45 +01:00
parent 85dee61a4d
commit e280e4eadb
1967 changed files with 397327 additions and 74093 deletions

View File

@@ -0,0 +1,2 @@
# Set default behavior to automatically convert line endings
* text=auto eol=lf

View File

@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10

View File

@@ -0,0 +1,21 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 15
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- "discussion"
- "feature request"
- "bug"
- "help wanted"
- "plugin suggestion"
- "good first issue"
# Label to use when marking an issue as stale
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
closeComment: false

View File

@@ -0,0 +1,28 @@
name: CI
on:
push:
branches:
- main
- next
- 'v*'
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
permissions:
contents: read
jobs:
test:
permissions:
contents: write
pull-requests: write
uses: fastify/workflows/.github/workflows/plugins-ci.yml@v5
with:
license-check: true
lint: true

24
backend/node_modules/@fastify/proxy-addr/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,24 @@
MIT License
Copyright (c) 2014-2016 Douglas Christopher Wilson
Copyright (c) 2021-present The Fastify team
The Fastify team members are listed at https://github.com/fastify/fastify#team.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

141
backend/node_modules/@fastify/proxy-addr/README.md generated vendored Normal file
View File

@@ -0,0 +1,141 @@
# proxy-addr
[![CI](https://github.com/fastify/proxy-addr/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/fastify/proxy-addr/actions/workflows/ci.yml)
[![NPM version](https://img.shields.io/npm/v/@fastify/proxy-addr.svg?style=flat)](https://www.npmjs.com/package/@fastify/proxy-addr)
[![neostandard javascript style](https://img.shields.io/badge/code_style-neostandard-brightgreen?style=flat)](https://github.com/neostandard/neostandard)
Determine the address of a proxied request.
Forked from https://github.com/jshttp/proxy-addr to address https://github.com/jshttp/forwarded/pull/9.
## Install
```sh
$ npm i @fastify/proxy-addr
```
## API
<!-- eslint-disable no-unused-vars -->
```js
const proxyaddr = require('@fastify/proxy-addr')
```
### proxyaddr(req, trust)
Return the address of the request, using the given `trust` parameter.
The `trust` argument is a function that returns `true` if you trust
the address, `false` if you don't. The closest untrusted address is
returned.
<!-- eslint-disable no-undef -->
```js
proxyaddr(req, function (addr) { return addr === '127.0.0.1' })
proxyaddr(req, function (addr, i) { return i < 1 })
```
The `trust` argument may also be a single IP address string or an
array of trusted addresses, as plain IP addresses, CIDR-formatted
strings, or IP/netmask strings.
<!-- eslint-disable no-undef -->
```js
proxyaddr(req, '127.0.0.1')
proxyaddr(req, ['127.0.0.0/8', '10.0.0.0/8'])
proxyaddr(req, ['127.0.0.0/255.0.0.0', '192.168.0.0/255.255.0.0'])
```
This module also supports IPv6. Your IPv6 addresses will be normalized
automatically (i.e. `fe80::00ed:1` equals `fe80:0:0:0:0:0:ed:1`).
<!-- eslint-disable no-undef -->
```js
proxyaddr(req, '::1')
proxyaddr(req, ['::1/128', 'fe80::/10'])
```
This module will automatically work with IPv4-mapped IPv6 addresses
as well to support node.js in IPv6-only mode. This means that you do
not have to specify both `::ffff:a00:1` and `10.0.0.1`.
As a convenience, this module also takes certain pre-defined names
in addition to IP addresses, which expand into IP addresses:
<!-- eslint-disable no-undef -->
```js
proxyaddr(req, 'loopback')
proxyaddr(req, ['loopback', 'fc00:ac:1ab5:fff::1/64'])
```
* `loopback`: IPv4 and IPv6 loopback addresses (like `::1` and
`127.0.0.1`).
* `linklocal`: IPv4 and IPv6 link-local addresses (like
`fe80::1:1:1:1` and `169.254.0.1`).
* `uniquelocal`: IPv4 private addresses and IPv6 unique-local
addresses (like `fc00:ac:1ab5:fff::1` and `192.168.0.1`).
When `trust` is specified as a function, it will be called for each
address to determine if it is a trusted address. The function is
given two arguments: `addr` and `i`, where `addr` is a string of
the address to check and `i` is a number that represents the distance
from the socket address.
### proxyaddr.all(req, [trust])
Return all the request addresses, optionally stopping at the
first untrusted. This array is ordered from closest to furthest
(i.e. `arr[0] === req.connection.remoteAddress`).
<!-- eslint-disable no-undef -->
```js
proxyaddr.all(req)
```
The optional `trust` argument takes the same arguments as `trust`
does in `proxyaddr(req, trust)`.
<!-- eslint-disable no-undef -->
```js
proxyaddr.all(req, 'loopback')
```
### proxyaddr.compile(val)
Compiles argument `val` into a `trust` function. This function takes
the same arguments as `trust` does in `proxyaddr(req, trust)` and
returns a function suitable for `proxyaddr(req, trust)`.
<!-- eslint-disable no-undef, no-unused-vars -->
```js
const trust = proxyaddr.compile('loopback')
const addr = proxyaddr(req, trust)
```
This function is meant to be optimized for use against every request.
It is recommended that a trust function be compiled up-front for the
trusted configuration and passed to proxyaddr(req, trust) for each request.
## Testing
```sh
$ npm test
```
## Benchmarks
```sh
$ npm run bench
```
## License
Licensed under [MIT](./LICENSE).

View File

@@ -0,0 +1,50 @@
'use strict'
/**
* Globals for benchmark.js
*/
global.proxyaddr = require('..')
global.createReq = createReq
/**
* Module dependencies.
*/
const benchmark = require('benchmark')
const benchmarks = require('beautify-benchmark')
const suite = new benchmark.Suite()
suite.add({
name: 're-compiling',
minSamples: 100,
fn: 'proxyaddr(req, "loopback")',
setup: 'req = createReq("127.0.0.1", "10.0.0.1")'
})
suite.add({
name: 'pre-compiling',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile("loopback")'
})
suite.on('cycle', function onCycle (event) {
benchmarks.add(event.target)
})
suite.on('complete', function onComplete () {
benchmarks.log()
})
suite.run({ async: false })
function createReq (socketAddr, forwardedFor) {
return {
socket: {
remoteAddress: socketAddr
},
headers: {
'x-forwarded-for': (forwardedFor || '')
}
}
}

View File

@@ -0,0 +1,30 @@
'use strict'
const fs = require('node:fs')
const path = require('node:path')
const spawn = require('node:child_process').spawn
const exe = process.argv[0]
const cwd = process.cwd()
runScripts(fs.readdirSync(__dirname))
function runScripts (fileNames) {
const fileName = fileNames.shift()
if (!fileName) return
if (!/\.js$/i.test(fileName)) return runScripts(fileNames)
if (fileName.toLowerCase() === 'index.js') return runScripts(fileNames)
const fullPath = path.join(__dirname, fileName)
console.log('> %s %s', exe, path.relative(cwd, fullPath))
const proc = spawn(exe, [fullPath], {
stdio: 'inherit'
})
proc.on('exit', function () {
runScripts(fileNames)
})
}

View File

@@ -0,0 +1,57 @@
'use strict'
/**
* Globals for benchmark.js
*/
global.proxyaddr = require('..')
global.createReq = createReq
/**
* Module dependencies.
*/
const benchmark = require('benchmark')
const benchmarks = require('beautify-benchmark')
const suite = new benchmark.Suite()
suite.add({
name: 'ipv4',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile("127.0.0.1")'
})
suite.add({
name: 'ipv4-mapped',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("::ffff:7f00:1", "10.0.0.1"); trust = proxyaddr.compile("127.0.0.1")'
})
suite.add({
name: 'ipv6',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("::1", "10.0.0.1"); trust = proxyaddr.compile("::1")'
})
suite.on('cycle', function onCycle (event) {
benchmarks.add(event.target)
})
suite.on('complete', function onComplete () {
benchmarks.log()
})
suite.run({ async: false })
function createReq (socketAddr, forwardedFor) {
return {
socket: {
remoteAddress: socketAddr
},
headers: {
'x-forwarded-for': (forwardedFor || '')
}
}
}

View File

@@ -0,0 +1,78 @@
'use strict'
/**
* Globals for benchmark.js
*/
global.proxyaddr = require('..')
global.createReq = createReq
/**
* Module dependencies.
*/
const benchmark = require('benchmark')
const benchmarks = require('beautify-benchmark')
const suite = new benchmark.Suite()
suite.add({
name: 'trust none',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile([])'
})
suite.add({
name: 'trust all',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = function() {return true}'
})
suite.add({
name: 'trust single',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile("127.0.0.1")'
})
suite.add({
name: 'trust first',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = function(a, i) {return i<1}'
})
suite.add({
name: 'trust subnet',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile("127.0.0.1/8")'
})
suite.add({
name: 'trust multiple',
minSamples: 100,
fn: 'proxyaddr(req, trust)',
setup: 'req = createReq("127.0.0.1", "10.0.0.1"); trust = proxyaddr.compile(["127.0.0.1", "10.0.0.1"])'
})
suite.on('cycle', function onCycle (event) {
benchmarks.add(event.target)
})
suite.on('complete', function onComplete () {
benchmarks.log()
})
suite.run({ async: false })
function createReq (socketAddr, forwardedFor) {
return {
socket: {
remoteAddress: socketAddr
},
headers: {
'x-forwarded-for': (forwardedFor || '')
}
}
}

View File

@@ -0,0 +1,6 @@
'use strict'
module.exports = require('neostandard')({
ignores: require('neostandard').resolveIgnoresFromGitignore(),
ts: true
})

331
backend/node_modules/@fastify/proxy-addr/index.js generated vendored Normal file
View File

@@ -0,0 +1,331 @@
/*!
* proxy-addr
* Copyright(c) 2021 Fastify collaborators
* Copyright(c) 2014-2016 Douglas Christopher Wilson
* MIT Licensed
*/
'use strict'
/**
* Module exports.
* @public
*/
module.exports = proxyaddr
module.exports.default = proxyaddr
module.exports.proxyaddr = proxyaddr
module.exports.all = alladdrs
module.exports.compile = compile
/**
* Module dependencies.
* @private
*/
const forwarded = require('@fastify/forwarded')
const ipaddr = require('ipaddr.js')
/**
* Variables.
* @private
*/
const DIGIT_REGEXP = /^\d+$/u
const isip = ipaddr.isValid
const parseip = ipaddr.parse
/**
* Pre-defined IP ranges.
* @private
*/
const IP_RANGES = {
linklocal: ['169.254.0.0/16', 'fe80::/10'],
loopback: ['127.0.0.1/8', '::1/128'],
uniquelocal: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', 'fc00::/7']
}
/**
* Get all addresses in the request, optionally stopping
* at the first untrusted.
*
* @param {Object} request
* @param {Function|Array|String} [trust]
* @public
*/
function alladdrs (req, trust) {
// get addresses
const addrs = forwarded(req)
if (!trust) {
// Return all addresses
return addrs
}
if (typeof trust !== 'function') {
trust = compile(trust)
}
for (let i = 0; i < addrs.length - 1; i++) {
if (trust(addrs[i], i)) continue
addrs.length = i + 1
}
return addrs
}
/**
* Compile argument into trust function.
*
* @param {Array|String} val
* @private
*/
function compile (val) {
if (!val) {
throw new TypeError('argument is required')
}
let trust
if (typeof val === 'string') {
trust = [val]
} else if (Array.isArray(val)) {
trust = val.slice()
} else {
throw new TypeError('unsupported trust argument')
}
for (let i = 0; i < trust.length; i++) {
val = trust[i]
if (!Object.hasOwn(IP_RANGES, val)) {
continue
}
// Splice in pre-defined range
val = IP_RANGES[val]
trust.splice.apply(trust, [i, 1].concat(val))
i += val.length - 1
}
return compileTrust(compileRangeSubnets(trust))
}
/**
* Compile `arr` elements into range subnets.
*
* @param {Array} arr
* @private
*/
function compileRangeSubnets (arr) {
const rangeSubnets = new Array(arr.length)
for (let i = 0; i < arr.length; i++) {
rangeSubnets[i] = parseipNotation(arr[i])
}
return rangeSubnets
}
/**
* Compile range subnet array into trust function.
*
* @param {Array} rangeSubnets
* @private
*/
function compileTrust (rangeSubnets) {
// Return optimized function based on length
const len = rangeSubnets.length
return len === 0
? trustNone
: len === 1
? trustSingle(rangeSubnets[0])
: trustMulti(rangeSubnets)
}
/**
* Parse IP notation string into range subnet.
*
* @param {String} note
* @private
*/
function parseipNotation (note) {
const pos = note.lastIndexOf('/')
const str = pos !== -1
? note.substring(0, pos)
: note
if (!isip(str)) {
throw new TypeError('invalid IP address: ' + str)
}
let ip = parseip(str)
if (pos === -1 && ip.kind() === 'ipv6' && ip.isIPv4MappedAddress()) {
// Store as IPv4
ip = ip.toIPv4Address()
}
const max = ip.kind() === 'ipv6'
? 128
: 32
let range = pos !== -1
? note.substring(pos + 1, note.length)
: null
if (range === null) {
range = max
} else if (DIGIT_REGEXP.test(range)) {
range = parseInt(range, 10)
} else if (ip.kind() === 'ipv4' && isip(range)) {
range = parseNetmask(range)
} else {
range = null
}
if (range <= 0 || range > max) {
throw new TypeError('invalid range on address: ' + note)
}
return [ip, range]
}
/**
* Parse netmask string into CIDR range.
*
* @param {String} netmask
* @private
*/
function parseNetmask (netmask) {
const ip = parseip(netmask)
const kind = ip.kind()
return kind === 'ipv4'
? ip.prefixLengthFromSubnetMask()
: null
}
/**
* Determine address of proxied request.
*
* @param {Object} request
* @param {Function|Array|String} trust
* @public
*/
function proxyaddr (req, trust) {
if (!req) {
throw new TypeError('req argument is required')
}
if (!trust) {
throw new TypeError('trust argument is required')
}
const addrs = alladdrs(req, trust)
return addrs[addrs.length - 1]
}
/**
* Static trust function to trust nothing.
*
* @private
*/
function trustNone () {
return false
}
/**
* Compile trust function for multiple subnets.
*
* @param {Array} subnets
* @private
*/
function trustMulti (subnets) {
return function trust (addr) {
if (!isip(addr)) return false
const ip = parseip(addr)
let ipconv
const kind = ip.kind()
for (let i = 0; i < subnets.length; i++) {
const subnet = subnets[i]
const subnetip = subnet[0]
const subnetkind = subnetip.kind()
const subnetrange = subnet[1]
let trusted = ip
if (kind !== subnetkind) {
const subnetisipv4 = subnetkind === 'ipv4'
if (subnetisipv4 && !ip.isIPv4MappedAddress()) {
// Incompatible IP addresses
continue
}
if (!ipconv) {
// Convert IP to match subnet IP kind
ipconv = subnetisipv4
? ip.toIPv4Address()
: ip.toIPv4MappedAddress()
}
trusted = ipconv
}
if (trusted.match(subnetip, subnetrange)) {
return true
}
}
return false
}
}
/**
* Compile trust function for single subnet.
*
* @param {Object} subnet
* @private
*/
function trustSingle (subnet) {
const subnetip = subnet[0]
const subnetkind = subnetip.kind()
const subnetisipv4 = subnetkind === 'ipv4'
const subnetrange = subnet[1]
return function trust (addr) {
if (!isip(addr)) return false
let ip = parseip(addr)
const kind = ip.kind()
if (kind !== subnetkind) {
if (subnetisipv4 && !ip.isIPv4MappedAddress()) {
// Incompatible IP addresses
return false
}
// Convert IP to match subnet IP kind
ip = subnetisipv4
? ip.toIPv4Address()
: ip.toIPv4MappedAddress()
}
return ip.match(subnetip, subnetrange)
}
}

73
backend/node_modules/@fastify/proxy-addr/package.json generated vendored Normal file
View File

@@ -0,0 +1,73 @@
{
"name": "@fastify/proxy-addr",
"description": "Determine the address of a proxied request",
"version": "5.1.0",
"main": "index.js",
"type": "commonjs",
"types": "types/index.d.ts",
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
"contributors": [
{
"name": "Matteo Collina",
"email": "hello@matteocollina.com"
},
{
"name": "Aras Abbasi",
"email": "aras.abbasi@gmail.com"
},
{
"name": "James Sumners",
"url": "https://james.sumners.info"
},
{
"name": "Frazer Smith",
"email": "frazer.dev@icloud.com",
"url": "https://github.com/fdawgs"
}
],
"license": "MIT",
"keywords": [
"ip",
"proxy",
"x-forwarded-for"
],
"repository": {
"type": "git",
"url": "git+https://github.com/fastify/proxy-addr.git"
},
"bugs": {
"url": "https://github.com/fastify/proxy-addr/issues"
},
"homepage": "https://github.com/fastify/proxy-addr#readme",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"dependencies": {
"@fastify/forwarded": "^3.0.0",
"ipaddr.js": "^2.1.0"
},
"devDependencies": {
"@types/node": "^24.0.8",
"beautify-benchmark": "0.2.4",
"benchmark": "2.1.4",
"c8": "^10.1.2",
"eslint": "^9.17.0",
"neostandard": "^0.12.0",
"tsd": "^0.32.0"
},
"scripts": {
"bench": "node benchmark/index.js",
"lint": "eslint",
"lint:fix": "eslint --fix",
"test": "npm run test:unit && npm run test:typescript",
"test:typescript": "tsd",
"test:unit": "c8 --100 node --test"
}
}

View File

@@ -0,0 +1,55 @@
'use strict'
const { test } = require('node:test')
const proxyaddr = require('..')
test('argument req should be required', function (t) {
t.assert.throws(proxyaddr.all, /req.*required/u)
})
test('argument trustshould be optional', function (t) {
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.all.bind(null, req))
})
test('with no headers should return socket address', function (t) {
const req = createReq('127.0.0.1')
t.assert.deepStrictEqual(proxyaddr.all(req), ['127.0.0.1'])
})
test('with x-forwarded-for header should include x-forwarded-for', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': '10.0.0.1'
})
t.assert.deepStrictEqual(proxyaddr.all(req), ['127.0.0.1', '10.0.0.1'])
})
test('with x-forwarded-for header should include x-forwarded-for in correct order', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': '10.0.0.1, 10.0.0.2'
})
t.assert.deepStrictEqual(proxyaddr.all(req), ['127.0.0.1', '10.0.0.2', '10.0.0.1'])
})
test('with trust argument should stop at first untrusted', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': '10.0.0.1, 10.0.0.2'
})
t.assert.deepStrictEqual(proxyaddr.all(req, '127.0.0.1'), ['127.0.0.1', '10.0.0.2'])
})
test('with trust argument should be only socket address for no trust', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': '10.0.0.1, 10.0.0.2'
})
t.assert.deepStrictEqual(proxyaddr.all(req, []), ['127.0.0.1'])
})
function createReq (socketAddr, headers) {
return {
socket: {
remoteAddress: socketAddr
},
headers: headers || {}
}
}

View File

@@ -0,0 +1,365 @@
'use strict'
const { test } = require('node:test')
const proxyaddr = require('..')
test('req should be required', function (t) {
t.assert.throws(proxyaddr, /req.*required/u)
})
test('trust should be required', function (t) {
const req = createReq('127.0.0.1')
t.assert.throws(proxyaddr.bind(null, req), /trust.*required/u)
})
test('trust should accept a function', function (t) {
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.bind(null, req, all))
})
test('trust should accept an array', function (t) {
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.bind(null, req, []))
})
test('trust should accept a string', function (t) {
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.bind(null, req, '127.0.0.1'))
})
test('trust should reject a number', function (t) {
const req = createReq('127.0.0.1')
t.assert.throws(proxyaddr.bind(null, req, 42), /unsupported trust argument/u)
})
test('trust should accept IPv4', function (t) {
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.bind(null, req, '127.0.0.1'))
})
test('trust should accept IPv6', function (t) {
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.bind(null, req, '::1'))
})
test('trust should accept IPv4-style IPv6', function (t) {
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.bind(null, req, '::ffff:127.0.0.1'))
})
test('trust should accept pre-defined names', function (t) {
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.bind(null, req, 'loopback'))
})
test('trust should accept pre-defined names in array', function (t) {
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.bind(null, req, ['loopback', '10.0.0.1']))
})
test('trust should not alter input array', function (t) {
const arr = ['loopback', '10.0.0.1']
const req = createReq('127.0.0.1')
t.assert.doesNotThrow(proxyaddr.bind(null, req, arr))
t.assert.deepStrictEqual(arr, ['loopback', '10.0.0.1'])
})
test('trust should reject non-IP', function (t) {
const req = createReq('127.0.0.1')
t.assert.throws(proxyaddr.bind(null, req, 'blargh'), /invalid IP address/u)
t.assert.throws(proxyaddr.bind(null, req, '10.0.300.1'), /invalid IP address/u)
t.assert.throws(proxyaddr.bind(null, req, '::ffff:30.168.1.9000'), /invalid IP address/u)
t.assert.throws(proxyaddr.bind(null, req, '-1'), /invalid IP address/u)
})
test('trust should reject bad CIDR', function (t) {
const req = createReq('127.0.0.1')
t.assert.throws(proxyaddr.bind(null, req, '10.0.0.1/internet'), /invalid range on address/u)
t.assert.throws(proxyaddr.bind(null, req, '10.0.0.1/6000'), /invalid range on address/u)
t.assert.throws(proxyaddr.bind(null, req, '::1/6000'), /invalid range on address/u)
t.assert.throws(proxyaddr.bind(null, req, '::ffff:a00:2/136'), /invalid range on address/u)
t.assert.throws(proxyaddr.bind(null, req, '::ffff:a00:2/-1'), /invalid range on address/u)
})
test('trust should reject bad netmask', function (t) {
const req = createReq('127.0.0.1')
t.assert.throws(proxyaddr.bind(null, req, '10.0.0.1/255.0.255.0'), /invalid range on address/u)
t.assert.throws(proxyaddr.bind(null, req, '10.0.0.1/ffc0::'), /invalid range on address/u)
t.assert.throws(proxyaddr.bind(null, req, 'fe80::/ffc0::'), /invalid range on address/u)
t.assert.throws(proxyaddr.bind(null, req, 'fe80::/255.255.255.0'), /invalid range on address/u)
t.assert.throws(proxyaddr.bind(null, req, '::ffff:a00:2/255.255.255.0'), /invalid range on address/u)
})
test('trust should be invoked as trust(addr, i)', function (t) {
const log = []
const req = createReq('127.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.1'
})
proxyaddr(req, function () {
return log.push(Array.prototype.slice.call(arguments))
})
t.assert.deepStrictEqual(log, [
['127.0.0.1', 0],
['10.0.0.1', 1]
])
})
test('with all trusted should return socket address wtesth no headers', function (t) {
const req = createReq('127.0.0.1')
t.assert.strictEqual(proxyaddr(req, all), '127.0.0.1')
})
test('with all trusted should return header value', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': '10.0.0.1'
})
t.assert.strictEqual(proxyaddr(req, all), '10.0.0.1')
})
test('with all trusted should return furthest header value', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': '10.0.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, all), '10.0.0.1')
})
test('with none trusted should return socket address wtesth no headers', function (t) {
const req = createReq('127.0.0.1')
t.assert.strictEqual(proxyaddr(req, none), '127.0.0.1')
})
test('with none trusted should return socket address wtesth headers', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': '10.0.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, none), '127.0.0.1')
})
test('with some trusted should return socket address wtesth no headers', function (t) {
const req = createReq('127.0.0.1')
t.assert.strictEqual(proxyaddr(req, trust10x), '127.0.0.1')
})
test('with some trusted should return socket address when not trusted', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': '10.0.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, trust10x), '127.0.0.1')
})
test('with some trusted should return header when socket trusted', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1'
})
t.assert.strictEqual(proxyaddr(req, trust10x), '192.168.0.1')
})
test('with some trusted should return first untrusted after trusted', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, trust10x), '192.168.0.1')
})
test('with some trusted should not skip untrusted', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '10.0.0.3, 192.168.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, trust10x), '192.168.0.1')
})
test('when given array should accept ltesteral IP addresses', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, ['10.0.0.1', '10.0.0.2']), '192.168.0.1')
})
test('when given array should not trust non-IP addresses', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.2, localhost'
})
t.assert.strictEqual(proxyaddr(req, ['10.0.0.1', '10.0.0.2']), 'localhost')
})
test('when given array should return socket address if none match', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, ['127.0.0.1', '192.168.0.100']), '10.0.0.1')
})
test('when array empty should return socket address ', function (t) {
const req = createReq('127.0.0.1')
t.assert.strictEqual(proxyaddr(req, []), '127.0.0.1')
})
test('when array empty should return socket address wtesth headers', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': '10.0.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, []), '127.0.0.1')
})
test('when given IPv4 addresses should accept ltesteral IP addresses', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, ['10.0.0.1', '10.0.0.2']), '192.168.0.1')
})
test('when given IPv4 addresses should accept CIDR notation', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.200'
})
t.assert.strictEqual(proxyaddr(req, '10.0.0.2/26'), '10.0.0.200')
})
test('when given IPv4 addresses should accept netmask notation', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.200'
})
t.assert.strictEqual(proxyaddr(req, '10.0.0.2/255.255.255.192'), '10.0.0.200')
})
test('when given IPv6 addresses should accept ltesteral IP addresses', function (t) {
const req = createReq('fe80::1', {
'x-forwarded-for': '2002:c000:203::1, fe80::2'
})
t.assert.strictEqual(proxyaddr(req, ['fe80::1', 'fe80::2']), '2002:c000:203::1')
})
test('when given IPv6 addresses should accept CIDR notation', function (t) {
const req = createReq('fe80::1', {
'x-forwarded-for': '2002:c000:203::1, fe80::ff00'
})
t.assert.strictEqual(proxyaddr(req, 'fe80::/125'), 'fe80::ff00')
})
test('with IP version mixed should match respective versions', function (t) {
const req = createReq('::1', {
'x-forwarded-for': '2002:c000:203::1'
})
t.assert.strictEqual(proxyaddr(req, ['127.0.0.1', '::1']), '2002:c000:203::1')
})
test('with IP version mixed should not match IPv4 to IPv6', function (t) {
const req = createReq('::1', {
'x-forwarded-for': '2002:c000:203::1'
})
t.assert.strictEqual(proxyaddr(req, '127.0.0.1'), '::1')
})
test('when IPv4-mapped IPv6 addresses should match IPv4 trust to IPv6 request', function (t) {
const req = createReq('::ffff:a00:1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, ['10.0.0.1', '10.0.0.2']), '192.168.0.1')
})
test('when IPv4-mapped IPv6 addresses should match IPv4 netmask trust to IPv6 request', function (t) {
const req = createReq('::ffff:a00:1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, ['10.0.0.1/16']), '192.168.0.1')
})
test('when IPv4-mapped IPv6 addresses should match IPv6 trust to IPv4 request', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.2'
})
t.assert.strictEqual(proxyaddr(req, ['::ffff:a00:1', '::ffff:a00:2']), '192.168.0.1')
})
test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped address', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.200'
})
t.assert.strictEqual(proxyaddr(req, '::ffff:a00:2/122'), '10.0.0.200')
})
test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped address mixed wtesth IPv6 CIDR', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.200'
})
t.assert.strictEqual(proxyaddr(req, ['::ffff:a00:2/122', 'fe80::/125']), '10.0.0.200')
})
test('when IPv4-mapped IPv6 addresses should match CIDR notation for IPv4-mapped address mixed wtesth IPv4 addresses', function (t) {
const req = createReq('10.0.0.1', {
'x-forwarded-for': '192.168.0.1, 10.0.0.200'
})
t.assert.strictEqual(proxyaddr(req, ['::ffff:a00:2/122', '127.0.0.1']), '10.0.0.200')
})
test('when given predefined names should accept single pre-defined name', function (t) {
const req = createReq('fe80::1', {
'x-forwarded-for': '2002:c000:203::1, fe80::2'
})
t.assert.strictEqual(proxyaddr(req, 'linklocal'), '2002:c000:203::1')
})
test('when given predefined names should accept multiple pre-defined names', function (t) {
const req = createReq('::1', {
'x-forwarded-for': '2002:c000:203::1, fe80::2'
})
t.assert.strictEqual(proxyaddr(req, ['loopback', 'linklocal']), '2002:c000:203::1')
})
test('when header contains non-ip addresses should stop at first non-ip after trusted', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': 'myrouter, 127.0.0.1, proxy'
})
t.assert.strictEqual(proxyaddr(req, '127.0.0.1'), 'proxy')
})
test('when header contains non-ip addresses should stop at first malformed ip after trusted', function (t) {
const req = createReq('127.0.0.1', {
'x-forwarded-for': 'myrouter, 127.0.0.1, ::8:8:8:8:8:8:8:8:8'
})
t.assert.strictEqual(proxyaddr(req, '127.0.0.1'), '::8:8:8:8:8:8:8:8:8')
})
test('when header contains non-ip addresses should provide all values to function', function (t) {
const log = []
const req = createReq('127.0.0.1', {
'x-forwarded-for': 'myrouter, 127.0.0.1, proxy'
})
proxyaddr(req, function () {
return log.push(Array.prototype.slice.call(arguments))
})
t.assert.deepStrictEqual(log, [
['127.0.0.1', 0],
['proxy', 1],
['127.0.0.1', 2]
])
})
test('when socket address undefined should return undefined as address', function (t) {
const req = createReq(undefined)
t.assert.strictEqual(proxyaddr(req, '127.0.0.1'), undefined)
})
test('when socket address undefined should return undefined even wtesth trusted headers', function (t) {
const req = createReq(undefined, {
'x-forwarded-for': '127.0.0.1, 10.0.0.1'
})
t.assert.strictEqual(proxyaddr(req, '127.0.0.1'), undefined)
})
function createReq (socketAddr, headers) {
return {
socket: {
remoteAddress: socketAddr
},
headers: headers || {}
}
}
function all () { return true }
function none () { return false }
function trust10x (addr) { return /^10\./u.test(addr) }

View File

@@ -0,0 +1,58 @@
'use strict'
const { test } = require('node:test')
const proxyaddr = require('..')
test('trust arg should be required', function (t) {
t.assert.throws(proxyaddr.compile, /argument.*required/u)
})
test('trust arg should accept an array', function (t) {
t.assert.strictEqual(typeof proxyaddr.compile([]), 'function')
})
test('trust arg should accept a string', function (t) {
t.assert.strictEqual(typeof proxyaddr.compile('127.0.0.1'), 'function')
})
test('trust arg should reject a number', function (t) {
t.assert.throws(proxyaddr.compile.bind(null, 42), /unsupported trust argument/u)
})
test('trust arg should accept IPv4', function (t) {
t.assert.strictEqual(typeof proxyaddr.compile('127.0.0.1'), 'function')
})
test('trust arg should accept IPv6', function (t) {
t.assert.strictEqual(typeof proxyaddr.compile('::1'), 'function')
})
test('trust arg should accept IPv4-style IPv6', function (t) {
t.assert.strictEqual(typeof proxyaddr.compile('::ffff:127.0.0.1'), 'function')
})
test('trust arg should accept pre-defined names', function (t) {
t.assert.strictEqual(typeof proxyaddr.compile('loopback'), 'function')
})
test('trust arg should accept pre-defined names in array', function (t) {
t.assert.strictEqual(typeof proxyaddr.compile(['loopback', '10.0.0.1']), 'function')
})
test('trust arg should reject non-IP', function (t) {
t.assert.throws(proxyaddr.compile.bind(null, 'blargh'), /invalid IP address/u)
t.assert.throws(proxyaddr.compile.bind(null, '-1'), /invalid IP address/u)
})
test('trust arg should reject bad CIDR', function (t) {
t.assert.throws(proxyaddr.compile.bind(null, '10.0.0.1/6000'), /invalid range on address/u)
t.assert.throws(proxyaddr.compile.bind(null, '::1/6000'), /invalid range on address/u)
t.assert.throws(proxyaddr.compile.bind(null, '::ffff:a00:2/136'), /invalid range on address/u)
t.assert.throws(proxyaddr.compile.bind(null, '::ffff:a00:2/-46'), /invalid range on address/u)
})
test('trust arg should not alter input array', function (t) {
const arr = ['loopback', '10.0.0.1']
t.assert.strictEqual(typeof proxyaddr.compile(arr), 'function')
t.assert.deepStrictEqual(arr, ['loopback', '10.0.0.1'])
})

View File

@@ -0,0 +1,18 @@
/// <reference types="node" />
import { IncomingMessage } from 'node:http'
type FastifyProxyAddr = typeof proxyaddr
declare function proxyaddr (req: IncomingMessage, trust: proxyaddr.Address | proxyaddr.Address[] | ((addr: string, i: number) => boolean)): string
declare namespace proxyaddr {
export function all (req: IncomingMessage, trust?: Address | Address[] | ((addr: string, i: number) => boolean)): string[]
export function compile (val: Address | Address[]): (addr: string, i: number) => boolean
export type Address = 'loopback' | 'linklocal' | 'uniquelocal' | string
export const proxyAddr: FastifyProxyAddr
export { proxyAddr as default }
}
export = proxyaddr

View File

@@ -0,0 +1,27 @@
import proxyaddr from '..'
import { createServer } from 'node:http'
import { expectType } from 'tsd'
createServer(req => {
expectType<string>(proxyaddr(req, addr => addr === '127.0.0.1'))
expectType<string>(proxyaddr(req, (_addr, i) => i < 1))
expectType<string>(proxyaddr(req, '127.0.0.1'))
expectType<string>(proxyaddr(req, ['127.0.0.0/8', '10.0.0.0/8']))
expectType<string>(proxyaddr(req, ['127.0.0.0/255.0.0.0', '192.168.0.0/255.255.0.0']))
expectType<string>(proxyaddr(req, '::1'))
expectType<string>(proxyaddr(req, ['::1/128', 'fe80::/10']))
expectType<string>(proxyaddr(req, 'loopback'))
expectType<string>(proxyaddr(req, ['loopback', 'fc00:ac:1ab5:fff::1/64']))
expectType<string[]>(proxyaddr.all(req))
expectType<string[]>(proxyaddr.all(req, 'loopback'))
proxyaddr.compile(['localhost'])
const trust = proxyaddr.compile('localhost')
expectType<(addr: string, i: number) => boolean>(trust)
proxyaddr(req, trust)
})