fastify 5.12.0 → 5.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/Reference/Server.md +8 -3
- package/docs/Reference/Validation-and-Serialization.md +11 -0
- package/docs/Reference/Warnings.md +11 -0
- package/fastify.d.ts +1 -1
- package/fastify.js +2 -2
- package/lib/four-oh-four.js +10 -22
- package/lib/request.js +3 -2
- package/lib/route.js +9 -5
- package/lib/schemas.js +5 -2
- package/lib/validation.js +206 -24
- package/lib/warnings.js +10 -1
- package/package.json +1 -1
- package/test/404s.test.js +105 -7
- package/test/close.test.js +34 -0
- package/test/fix-root-primitive-coercion.test.js +35 -0
- package/test/internals/reply.test.js +1 -1
- package/test/route-prefix.test.js +32 -0
- package/test/router-options.test.js +24 -0
- package/test/schema-feature.test.js +158 -0
- package/test/schema-special-usage.test.js +81 -0
- package/test/schema-validation.test.js +378 -0
- package/test/trust-proxy.test.js +27 -21
- package/test/types/fastify.tst.ts +2 -0
|
@@ -7,6 +7,8 @@ const AJV = require('ajv')
|
|
|
7
7
|
const Schema = require('fluent-json-schema')
|
|
8
8
|
const { waitForCb } = require('./helper')
|
|
9
9
|
const { kRequestContentType } = require('../lib/symbols')
|
|
10
|
+
const { spyWarning } = require('process-warning')
|
|
11
|
+
const { FSTSEC002 } = require('../lib/warnings')
|
|
10
12
|
|
|
11
13
|
const customSchemaCompilers = {
|
|
12
14
|
body: new AJV({
|
|
@@ -1615,3 +1617,379 @@ test('coercion of empty string to null with nullable types', async t => {
|
|
|
1615
1617
|
})
|
|
1616
1618
|
assert.strictEqual(JSON.parse(res.payload).param, null)
|
|
1617
1619
|
})
|
|
1620
|
+
|
|
1621
|
+
test('header schema dependencies with canonical-case names are enforced', async t => {
|
|
1622
|
+
const fastify = Fastify()
|
|
1623
|
+
|
|
1624
|
+
fastify.get('/', {
|
|
1625
|
+
schema: {
|
|
1626
|
+
headers: {
|
|
1627
|
+
type: 'object',
|
|
1628
|
+
properties: {
|
|
1629
|
+
'X-Admin': { type: 'string', const: 'true' },
|
|
1630
|
+
'X-Admin-Token': { type: 'string', const: 'server-secret' }
|
|
1631
|
+
},
|
|
1632
|
+
dependencies: {
|
|
1633
|
+
'X-Admin': ['X-Admin-Token']
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}, async () => {
|
|
1638
|
+
return { adminAction: true }
|
|
1639
|
+
})
|
|
1640
|
+
|
|
1641
|
+
await fastify.ready()
|
|
1642
|
+
|
|
1643
|
+
// Missing the token required by the dependency must be rejected even though
|
|
1644
|
+
// the dependency trigger is written in canonical case. Node.js stores the
|
|
1645
|
+
// received header as `x-admin`, and the normalized schema must lowercase the
|
|
1646
|
+
// dependency trigger so Ajv sees it and enforces the token assertion.
|
|
1647
|
+
const missingToken = await fastify.inject({
|
|
1648
|
+
method: 'GET',
|
|
1649
|
+
url: '/',
|
|
1650
|
+
headers: { 'X-Admin': 'true' }
|
|
1651
|
+
})
|
|
1652
|
+
t.assert.strictEqual(missingToken.statusCode, 400)
|
|
1653
|
+
t.assert.strictEqual(missingToken.json().code, 'FST_ERR_VALIDATION')
|
|
1654
|
+
|
|
1655
|
+
// Direct property constraints remain active: a wrong token is rejected too.
|
|
1656
|
+
const wrongToken = await fastify.inject({
|
|
1657
|
+
method: 'GET',
|
|
1658
|
+
url: '/',
|
|
1659
|
+
headers: { 'X-Admin': 'true', 'X-Admin-Token': 'wrong' }
|
|
1660
|
+
})
|
|
1661
|
+
t.assert.strictEqual(wrongToken.statusCode, 400)
|
|
1662
|
+
t.assert.strictEqual(wrongToken.json().code, 'FST_ERR_VALIDATION')
|
|
1663
|
+
|
|
1664
|
+
const valid = await fastify.inject({
|
|
1665
|
+
method: 'GET',
|
|
1666
|
+
url: '/',
|
|
1667
|
+
headers: { 'X-Admin': 'true', 'X-Admin-Token': 'server-secret' }
|
|
1668
|
+
})
|
|
1669
|
+
t.assert.strictEqual(valid.statusCode, 200)
|
|
1670
|
+
t.assert.deepStrictEqual(valid.json(), { adminAction: true })
|
|
1671
|
+
})
|
|
1672
|
+
|
|
1673
|
+
test('header schema dependencies: lowercase-equivalent schema behaves identically', async t => {
|
|
1674
|
+
const fastify = Fastify()
|
|
1675
|
+
|
|
1676
|
+
fastify.get('/', {
|
|
1677
|
+
schema: {
|
|
1678
|
+
headers: {
|
|
1679
|
+
type: 'object',
|
|
1680
|
+
properties: {
|
|
1681
|
+
'x-admin': { type: 'string', const: 'true' },
|
|
1682
|
+
'x-admin-token': { type: 'string', const: 'server-secret' }
|
|
1683
|
+
},
|
|
1684
|
+
dependencies: {
|
|
1685
|
+
'x-admin': ['x-admin-token']
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}, async () => {
|
|
1690
|
+
return { adminAction: true }
|
|
1691
|
+
})
|
|
1692
|
+
|
|
1693
|
+
await fastify.ready()
|
|
1694
|
+
|
|
1695
|
+
const missingToken = await fastify.inject({
|
|
1696
|
+
method: 'GET',
|
|
1697
|
+
url: '/',
|
|
1698
|
+
headers: { 'X-Admin': 'true' }
|
|
1699
|
+
})
|
|
1700
|
+
t.assert.strictEqual(missingToken.statusCode, 400)
|
|
1701
|
+
t.assert.strictEqual(missingToken.json().code, 'FST_ERR_VALIDATION')
|
|
1702
|
+
|
|
1703
|
+
const wrongToken = await fastify.inject({
|
|
1704
|
+
method: 'GET',
|
|
1705
|
+
url: '/',
|
|
1706
|
+
headers: { 'X-Admin': 'true', 'X-Admin-Token': 'wrong' }
|
|
1707
|
+
})
|
|
1708
|
+
t.assert.strictEqual(wrongToken.statusCode, 400)
|
|
1709
|
+
t.assert.strictEqual(wrongToken.json().code, 'FST_ERR_VALIDATION')
|
|
1710
|
+
|
|
1711
|
+
const valid = await fastify.inject({
|
|
1712
|
+
method: 'GET',
|
|
1713
|
+
url: '/',
|
|
1714
|
+
headers: { 'X-Admin': 'true', 'X-Admin-Token': 'server-secret' }
|
|
1715
|
+
})
|
|
1716
|
+
t.assert.strictEqual(valid.statusCode, 200)
|
|
1717
|
+
t.assert.deepStrictEqual(valid.json(), { adminAction: true })
|
|
1718
|
+
})
|
|
1719
|
+
|
|
1720
|
+
test('header schema dependencies are normalized in nested subschemas', async t => {
|
|
1721
|
+
const fastify = Fastify()
|
|
1722
|
+
|
|
1723
|
+
// The dependency lives inside an `allOf` subschema; its trigger and dependent
|
|
1724
|
+
// names must be lowercased the same way as root-level `dependencies`.
|
|
1725
|
+
fastify.get('/', {
|
|
1726
|
+
schema: {
|
|
1727
|
+
headers: {
|
|
1728
|
+
type: 'object',
|
|
1729
|
+
allOf: [{
|
|
1730
|
+
properties: { 'X-Admin': { type: 'string', const: 'true' } },
|
|
1731
|
+
dependencies: { 'X-Admin': ['X-Admin-Token'] }
|
|
1732
|
+
}]
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}, async () => {
|
|
1736
|
+
return { ok: true }
|
|
1737
|
+
})
|
|
1738
|
+
|
|
1739
|
+
await fastify.ready()
|
|
1740
|
+
|
|
1741
|
+
const missingToken = await fastify.inject({
|
|
1742
|
+
method: 'GET',
|
|
1743
|
+
url: '/',
|
|
1744
|
+
headers: { 'X-Admin': 'true' }
|
|
1745
|
+
})
|
|
1746
|
+
t.assert.strictEqual(missingToken.statusCode, 400)
|
|
1747
|
+
t.assert.strictEqual(missingToken.json().code, 'FST_ERR_VALIDATION')
|
|
1748
|
+
|
|
1749
|
+
const valid = await fastify.inject({
|
|
1750
|
+
method: 'GET',
|
|
1751
|
+
url: '/',
|
|
1752
|
+
headers: { 'X-Admin': 'true', 'X-Admin-Token': 'server-secret' }
|
|
1753
|
+
})
|
|
1754
|
+
t.assert.strictEqual(valid.statusCode, 200)
|
|
1755
|
+
})
|
|
1756
|
+
|
|
1757
|
+
test('header schema dependencies are normalized in local $ref definitions', async t => {
|
|
1758
|
+
const fastify = Fastify()
|
|
1759
|
+
|
|
1760
|
+
fastify.get('/', {
|
|
1761
|
+
schema: {
|
|
1762
|
+
headers: {
|
|
1763
|
+
$ref: '#/definitions/Headers',
|
|
1764
|
+
definitions: {
|
|
1765
|
+
Headers: {
|
|
1766
|
+
type: 'object',
|
|
1767
|
+
properties: {
|
|
1768
|
+
'X-Admin': { type: 'string', const: 'true' },
|
|
1769
|
+
'X-Admin-Token': { type: 'string', const: 'server-secret' }
|
|
1770
|
+
},
|
|
1771
|
+
dependencies: {
|
|
1772
|
+
'X-Admin': ['X-Admin-Token']
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
}, async () => {
|
|
1779
|
+
return { ok: true }
|
|
1780
|
+
})
|
|
1781
|
+
|
|
1782
|
+
await fastify.ready()
|
|
1783
|
+
|
|
1784
|
+
const missingToken = await fastify.inject({
|
|
1785
|
+
method: 'GET',
|
|
1786
|
+
url: '/',
|
|
1787
|
+
headers: { 'X-Admin': 'true' }
|
|
1788
|
+
})
|
|
1789
|
+
t.assert.strictEqual(missingToken.statusCode, 400)
|
|
1790
|
+
t.assert.strictEqual(missingToken.json().code, 'FST_ERR_VALIDATION')
|
|
1791
|
+
|
|
1792
|
+
const valid = await fastify.inject({
|
|
1793
|
+
method: 'GET',
|
|
1794
|
+
url: '/',
|
|
1795
|
+
headers: { 'X-Admin': 'true', 'X-Admin-Token': 'server-secret' }
|
|
1796
|
+
})
|
|
1797
|
+
t.assert.strictEqual(valid.statusCode, 200)
|
|
1798
|
+
})
|
|
1799
|
+
|
|
1800
|
+
test('header schema dependencies: subschema-form dependency values are normalized', async t => {
|
|
1801
|
+
const fastify = Fastify()
|
|
1802
|
+
|
|
1803
|
+
fastify.get('/', {
|
|
1804
|
+
schema: {
|
|
1805
|
+
headers: {
|
|
1806
|
+
type: 'object',
|
|
1807
|
+
properties: { 'X-Admin': { type: 'string', const: 'true' } },
|
|
1808
|
+
dependencies: {
|
|
1809
|
+
'X-Admin': {
|
|
1810
|
+
properties: { 'X-Admin-Token': { type: 'string', const: 'server-secret' } },
|
|
1811
|
+
required: ['X-Admin-Token']
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
}, async () => {
|
|
1817
|
+
return { ok: true }
|
|
1818
|
+
})
|
|
1819
|
+
|
|
1820
|
+
await fastify.ready()
|
|
1821
|
+
|
|
1822
|
+
const missingToken = await fastify.inject({
|
|
1823
|
+
method: 'GET',
|
|
1824
|
+
url: '/',
|
|
1825
|
+
headers: { 'X-Admin': 'true' }
|
|
1826
|
+
})
|
|
1827
|
+
t.assert.strictEqual(missingToken.statusCode, 400)
|
|
1828
|
+
t.assert.strictEqual(missingToken.json().code, 'FST_ERR_VALIDATION')
|
|
1829
|
+
|
|
1830
|
+
const valid = await fastify.inject({
|
|
1831
|
+
method: 'GET',
|
|
1832
|
+
url: '/',
|
|
1833
|
+
headers: { 'X-Admin': 'true', 'X-Admin-Token': 'server-secret' }
|
|
1834
|
+
})
|
|
1835
|
+
t.assert.strictEqual(valid.statusCode, 200)
|
|
1836
|
+
})
|
|
1837
|
+
|
|
1838
|
+
test('header schema lowercasing does not mutate the input schema', async t => {
|
|
1839
|
+
const headers = {
|
|
1840
|
+
type: 'object',
|
|
1841
|
+
properties: {
|
|
1842
|
+
'X-Admin': { type: 'string', const: 'true' },
|
|
1843
|
+
'X-Admin-Token': { type: 'string', const: 'server-secret' }
|
|
1844
|
+
},
|
|
1845
|
+
dependencies: {
|
|
1846
|
+
'X-Admin': ['X-Admin-Token']
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
const schema = { headers }
|
|
1850
|
+
const schemaBefore = JSON.stringify(schema)
|
|
1851
|
+
const headersBefore = JSON.stringify(headers)
|
|
1852
|
+
|
|
1853
|
+
const fastify = Fastify()
|
|
1854
|
+
fastify.get('/', { schema }, async () => {
|
|
1855
|
+
return { ok: true }
|
|
1856
|
+
})
|
|
1857
|
+
await fastify.ready()
|
|
1858
|
+
|
|
1859
|
+
t.assert.strictEqual(JSON.stringify(schema), schemaBefore)
|
|
1860
|
+
t.assert.strictEqual(JSON.stringify(headers), headersBefore)
|
|
1861
|
+
})
|
|
1862
|
+
|
|
1863
|
+
test('header schema with an external $ref emits FSTSEC002 (case-normalization does not reach it)', async t => {
|
|
1864
|
+
const spyData = spyWarning(FSTSEC002)
|
|
1865
|
+
t.after(spyData.restore)
|
|
1866
|
+
|
|
1867
|
+
const fastify = Fastify()
|
|
1868
|
+
fastify.addSchema({
|
|
1869
|
+
$id: 'http://example.com/admin-headers',
|
|
1870
|
+
type: 'object',
|
|
1871
|
+
properties: {
|
|
1872
|
+
'X-Admin': { type: 'string', const: 'true' },
|
|
1873
|
+
'X-Admin-Token': { type: 'string', const: 'server-secret' }
|
|
1874
|
+
},
|
|
1875
|
+
dependencies: { 'X-Admin': ['X-Admin-Token'] }
|
|
1876
|
+
})
|
|
1877
|
+
fastify.post('/', {
|
|
1878
|
+
schema: { headers: { $ref: 'http://example.com/admin-headers#' } }
|
|
1879
|
+
}, async () => ({ ok: true }))
|
|
1880
|
+
|
|
1881
|
+
await fastify.ready()
|
|
1882
|
+
|
|
1883
|
+
t.assert.strictEqual(spyData.callCount(), 1)
|
|
1884
|
+
t.assert.deepStrictEqual(spyData.calls[0].arguments, ['POST', '/', 'http://example.com/admin-headers#'])
|
|
1885
|
+
})
|
|
1886
|
+
|
|
1887
|
+
test('inline and local $ref header schemas do not emit FSTSEC002', async t => {
|
|
1888
|
+
const spyData = spyWarning(FSTSEC002)
|
|
1889
|
+
t.after(spyData.restore)
|
|
1890
|
+
|
|
1891
|
+
const fastify = Fastify()
|
|
1892
|
+
// inline header schema
|
|
1893
|
+
fastify.get('/inline', {
|
|
1894
|
+
schema: {
|
|
1895
|
+
headers: {
|
|
1896
|
+
type: 'object',
|
|
1897
|
+
properties: { 'X-Admin': { type: 'string' } },
|
|
1898
|
+
dependencies: { 'X-Admin': ['X-Admin-Token'] }
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
}, async () => ({ ok: true }))
|
|
1902
|
+
// local same-document $ref header schema
|
|
1903
|
+
fastify.get('/local', {
|
|
1904
|
+
schema: {
|
|
1905
|
+
headers: {
|
|
1906
|
+
type: 'object',
|
|
1907
|
+
$ref: '#/definitions/h',
|
|
1908
|
+
definitions: {
|
|
1909
|
+
h: {
|
|
1910
|
+
type: 'object',
|
|
1911
|
+
properties: { 'X-Admin': { type: 'string' } },
|
|
1912
|
+
dependencies: { 'X-Admin': ['X-Admin-Token'] }
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
}, async () => ({ ok: true }))
|
|
1918
|
+
|
|
1919
|
+
await fastify.ready()
|
|
1920
|
+
|
|
1921
|
+
t.assert.strictEqual(spyData.callCount(), 0)
|
|
1922
|
+
})
|
|
1923
|
+
|
|
1924
|
+
test('header schema normalization traverses dependent schema keywords', async t => {
|
|
1925
|
+
const fastify = Fastify({
|
|
1926
|
+
ajv: { customOptions: { strictSchema: false } }
|
|
1927
|
+
})
|
|
1928
|
+
|
|
1929
|
+
fastify.get('/', {
|
|
1930
|
+
schema: {
|
|
1931
|
+
headers: {
|
|
1932
|
+
type: 'object',
|
|
1933
|
+
dependentSchemas: {
|
|
1934
|
+
'X-Admin': {
|
|
1935
|
+
properties: { 'X-Admin-Token': { type: 'string' } }
|
|
1936
|
+
}
|
|
1937
|
+
},
|
|
1938
|
+
dependentRequired: {
|
|
1939
|
+
'X-Admin': ['X-Admin-Token']
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}, async () => ({ ok: true }))
|
|
1944
|
+
|
|
1945
|
+
await fastify.ready()
|
|
1946
|
+
t.assert.ok(true)
|
|
1947
|
+
})
|
|
1948
|
+
|
|
1949
|
+
test('header schema normalization preserves malformed keyword values for validation errors', async t => {
|
|
1950
|
+
const fastify = Fastify()
|
|
1951
|
+
|
|
1952
|
+
fastify.get('/', {
|
|
1953
|
+
schema: {
|
|
1954
|
+
headers: {
|
|
1955
|
+
type: 'object',
|
|
1956
|
+
properties: null,
|
|
1957
|
+
required: null,
|
|
1958
|
+
dependencies: null,
|
|
1959
|
+
dependentSchemas: null,
|
|
1960
|
+
dependentRequired: null,
|
|
1961
|
+
definitions: null
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
}, async () => ({ ok: true }))
|
|
1965
|
+
|
|
1966
|
+
await t.assert.rejects(fastify.ready())
|
|
1967
|
+
})
|
|
1968
|
+
|
|
1969
|
+
test('header schema detects an external $ref nested in an array', async t => {
|
|
1970
|
+
const spyData = spyWarning(FSTSEC002)
|
|
1971
|
+
t.after(spyData.restore)
|
|
1972
|
+
|
|
1973
|
+
const fastify = Fastify()
|
|
1974
|
+
fastify.addSchema({
|
|
1975
|
+
$id: 'http://example.com/nested-admin-headers',
|
|
1976
|
+
type: 'object',
|
|
1977
|
+
properties: { 'X-Admin': { type: 'string' } }
|
|
1978
|
+
})
|
|
1979
|
+
fastify.post('/', {
|
|
1980
|
+
schema: {
|
|
1981
|
+
headers: {
|
|
1982
|
+
allOf: [{ $ref: 'http://example.com/nested-admin-headers#' }]
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}, async () => ({ ok: true }))
|
|
1986
|
+
|
|
1987
|
+
await fastify.ready()
|
|
1988
|
+
|
|
1989
|
+
t.assert.strictEqual(spyData.callCount(), 1)
|
|
1990
|
+
t.assert.deepStrictEqual(spyData.calls[0].arguments, [
|
|
1991
|
+
'POST',
|
|
1992
|
+
'/',
|
|
1993
|
+
'http://example.com/nested-admin-headers#'
|
|
1994
|
+
])
|
|
1995
|
+
})
|
package/test/trust-proxy.test.js
CHANGED
|
@@ -106,20 +106,33 @@ test('trust proxy function', async t => {
|
|
|
106
106
|
await fetchForwardedRequest(fastifyServer, '1.1.1.1', '/trustproxyfunc', undefined, 'fastify.test:1234')
|
|
107
107
|
})
|
|
108
108
|
|
|
109
|
-
test('trust proxy number', async t => {
|
|
110
|
-
t.plan(
|
|
109
|
+
test('trust proxy number ignores forwarded headers', async t => {
|
|
110
|
+
t.plan(5)
|
|
111
111
|
const app = fastify({
|
|
112
112
|
trustProxy: 1
|
|
113
113
|
})
|
|
114
114
|
t.after(() => app.close())
|
|
115
115
|
|
|
116
116
|
app.get('/trustproxynumber', function (req, reply) {
|
|
117
|
-
|
|
117
|
+
t.assert.strictEqual(req.ip, '203.0.113.7', 'ip falls back to socket remote address')
|
|
118
|
+
t.assert.deepStrictEqual(req.ips, ['203.0.113.7'], 'ips falls back to socket remote address')
|
|
119
|
+
t.assert.strictEqual(req.host, 'app.example.com', 'host ignores x-forwarded-host')
|
|
120
|
+
t.assert.strictEqual(req.hostname, 'app.example.com', 'hostname ignores x-forwarded-host')
|
|
121
|
+
t.assert.strictEqual(req.protocol, 'http', 'protocol ignores x-forwarded-proto')
|
|
118
122
|
reply.code(200).send({ ip: req.ip, host: req.host })
|
|
119
123
|
})
|
|
120
124
|
|
|
121
|
-
|
|
122
|
-
|
|
125
|
+
await app.inject({
|
|
126
|
+
method: 'GET',
|
|
127
|
+
url: '/trustproxynumber',
|
|
128
|
+
remoteAddress: '203.0.113.7',
|
|
129
|
+
headers: {
|
|
130
|
+
host: 'app.example.com',
|
|
131
|
+
'x-forwarded-for': '9.9.9.9, 8.8.8.8',
|
|
132
|
+
'x-forwarded-host': 'evil.com',
|
|
133
|
+
'x-forwarded-proto': 'https'
|
|
134
|
+
}
|
|
135
|
+
})
|
|
123
136
|
})
|
|
124
137
|
|
|
125
138
|
test('trust proxy IP addresses', async t => {
|
|
@@ -241,18 +254,15 @@ test('trust proxy reads forwarded headers from trusted connections', async t =>
|
|
|
241
254
|
})
|
|
242
255
|
})
|
|
243
256
|
|
|
244
|
-
test('trust proxy with number and undefined socket remoteAddress', t => {
|
|
257
|
+
test('trust proxy with number and undefined socket remoteAddress ignores forwarded headers', t => {
|
|
245
258
|
t.plan(3)
|
|
246
259
|
|
|
247
|
-
// Test case for issue #6606: trustProxy: 1 with undefined/null socket.remoteAddress
|
|
248
|
-
// This simulates IISNode on Windows where socket.remoteAddress may be undefined
|
|
249
260
|
const headers = {
|
|
261
|
+
host: 'real.test',
|
|
250
262
|
'x-forwarded-for': '2.2.2.2, 1.1.1.1',
|
|
251
263
|
'x-forwarded-host': 'fastify.test',
|
|
252
264
|
'x-forwarded-proto': 'https'
|
|
253
265
|
}
|
|
254
|
-
// socket must exist but remoteAddress can be undefined
|
|
255
|
-
// This is what happens in IISNode with enableXFF="true"
|
|
256
266
|
const req = {
|
|
257
267
|
method: 'GET',
|
|
258
268
|
url: '/',
|
|
@@ -262,20 +272,16 @@ test('trust proxy with number and undefined socket remoteAddress', t => {
|
|
|
262
272
|
|
|
263
273
|
const TpRequest = buildRequest(Request, 1)
|
|
264
274
|
const request = new TpRequest('id', 'params', req, 'query', 'log')
|
|
265
|
-
|
|
266
|
-
t.assert.
|
|
267
|
-
|
|
268
|
-
// the hop count check should skip it and we get 1.1.1.1 (the first trusted address from X-Forwarded-For)
|
|
269
|
-
t.assert.strictEqual(request.ip, '1.1.1.1', 'gets ip from x-forwarded-for')
|
|
270
|
-
// The host should also work correctly
|
|
271
|
-
t.assert.strictEqual(request.host, 'fastify.test', 'gets host from x-forwarded-host')
|
|
275
|
+
t.assert.strictEqual(request.ip, undefined, 'ip falls back to undefined socket remote address')
|
|
276
|
+
t.assert.strictEqual(request.host, 'real.test', 'host ignores x-forwarded-host')
|
|
277
|
+
t.assert.strictEqual(request.protocol, 'http', 'protocol ignores x-forwarded-proto')
|
|
272
278
|
})
|
|
273
279
|
|
|
274
|
-
test('trust proxy with number and null socket remoteAddress', t => {
|
|
280
|
+
test('trust proxy with number and null socket remoteAddress ignores forwarded headers', t => {
|
|
275
281
|
t.plan(2)
|
|
276
282
|
|
|
277
|
-
// Test case for trustProxy: 1 with null socket.remoteAddress
|
|
278
283
|
const headers = {
|
|
284
|
+
host: 'real.test',
|
|
279
285
|
'x-forwarded-for': '2.2.2.2, 1.1.1.1',
|
|
280
286
|
'x-forwarded-host': 'fastify.test'
|
|
281
287
|
}
|
|
@@ -288,8 +294,8 @@ test('trust proxy with number and null socket remoteAddress', t => {
|
|
|
288
294
|
|
|
289
295
|
const TpRequest = buildRequest(Request, 1)
|
|
290
296
|
const request = new TpRequest('id', 'params', req, 'query', 'log')
|
|
291
|
-
t.assert.
|
|
292
|
-
t.assert.strictEqual(request.
|
|
297
|
+
t.assert.strictEqual(request.ip, null, 'ip falls back to null socket remote address')
|
|
298
|
+
t.assert.strictEqual(request.host, 'real.test', 'host ignores x-forwarded-host')
|
|
293
299
|
})
|
|
294
300
|
|
|
295
301
|
test('trust proxy does not trust x-forwarded-host/proto when socket is null', t => {
|
|
@@ -198,6 +198,8 @@ expect(fastify({
|
|
|
198
198
|
}
|
|
199
199
|
})).type.toBeAssignableTo<FastifyInstance>()
|
|
200
200
|
expect(fastify({ trustProxy: true })).type.toBeAssignableTo<FastifyInstance>()
|
|
201
|
+
// @ts-expect-error No overload matches this call.
|
|
202
|
+
fastify({ trustProxy: 1 })
|
|
201
203
|
expect(fastify({ querystringParser: () => ({ foo: 'bar' }) })).type.toBeAssignableTo<FastifyInstance>()
|
|
202
204
|
expect(fastify({ querystringParser: () => ({ foo: { bar: 'fuzz' } }) })).type.toBeAssignableTo<FastifyInstance>()
|
|
203
205
|
expect(fastify({ querystringParser: () => ({ foo: ['bar', 'fuzz'] }) })).type.toBeAssignableTo<FastifyInstance>()
|