Skip to content

Commit 6892bbd

Browse files
committed
fix: warn once, handle array header, add default trusted proxies, sort CONFIGURATION.md A-Z
1 parent 4bb9db3 commit 6892bbd

4 files changed

Lines changed: 84 additions & 54 deletions

File tree

CONFIGURATION.md

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -66,58 +66,55 @@ Running `nostream` for the first time creates the settings file in `<project_roo
6666

6767
| Name | Description |
6868
|---------------------------------------------|-------------------------------------------------------------------------------|
69-
| info.relay_url | Public-facing URL of your relay. (e.g. wss://relay.your-domain.com) |
70-
| info.name | Public name of your relay. (e.g. TBG's Public Relay) |
69+
| info.contact | Relay operator's contact. (e.g. mailto:operator@relay-your-domain.com) |
7170
| info.description | Public description of your relay. (e.g. Toronto Bitcoin Group Public Relay) |
71+
| info.name | Public name of your relay. (e.g. TBG's Public Relay) |
7272
| info.pubkey | Relay operator's Nostr pubkey in hex format. |
73-
| info.contact | Relay operator's contact. (e.g. mailto:operator@relay-your-domain.com) |
74-
| network.maxPayloadSize | Maximum number of bytes accepted per WebSocket frame |
75-
| network.remoteIpHeader | HTTP header from proxy containing IP address from client. |
76-
| network.trustedProxies | Optional allow-list of proxy IPs allowed to set `network.remoteIpHeader`; otherwise socket remote IP is used. |
77-
| payments.enabled | Enabled payments. Defaults to false. |
78-
| payments.processor | Either `zebedee`, `lnbits`, `lnurl`. |
79-
| payments.feeSchedules.admission[].enabled | Enables admission fee. Defaults to false. |
80-
| payments.feeSchedules.admission[].amount | Admission fee amount in msats. |
81-
| payments.feeSchedules.admission[].whitelists.pubkeys | List of pubkeys to waive admission fee. |
82-
| payments.feeSchedules.admission[].whitelists.event_kinds | List of event kinds to waive admission fee. Use `[min, max]` for ranges. |
83-
| paymentProcessors.zebedee.baseURL | Zebedee's API base URL. |
84-
| paymentProcessors.zebedee.callbackBaseURL | Public-facing Nostream's Zebedee Callback URL (e.g. https://relay.your-domain.com/callbacks/zebedee) |
85-
| paymentProcessors.zebedee.ipWhitelist | List with Zebedee's API Production IPs. See [ZBD API Documentation](https://api-reference.zebedee.io/#c7e18276-6935-4cca-89ae-ad949efe9a6a) for more info. |
86-
| paymentProcessors.lnbits.baseURL | Base URL of your Lnbits instance. |
87-
| paymentProcessors.lnbits.callbackBaseURL | Public-facing Nostream's Lnbits Callback URL. (e.g. https://relay.your-domain.com/callbacks/lnbits) |
88-
| paymentProcessors.lnurl.invoiceURL | [LUD-06 Pay Request](https://github.com/lnurl/luds/blob/luds/06.md) provider URL. (e.g. https://getalby.com/lnurlp/your-username) |
89-
| mirroring.static[].address | Address of mirrored relay. (e.g. ws://100.100.100.100:8008) |
90-
| mirroring.static[].filters | Subscription filters used to mirror. |
91-
| mirroring.static[].limits.event | Event limit overrides for this mirror. See configurations under limits.event. |
92-
| mirroring.static[].skipAdmissionCheck | Disable the admission fee check for events coming from this mirror. |
93-
| mirroring.static[].secret | Secret to pass to relays. Nostream relays only. Optional. |
94-
| workers.count | Number of workers to spin up to handle incoming connections. |
95-
| | Spin workers as many CPUs are available when set to zero. Defaults to zero. |
96-
| limits.event.eventId.minLeadingZeroBits | Leading zero bits required on every incoming event for proof of work. |
97-
| | Defaults to zero. Disabled when set to zero. |
98-
| limits.event.kind.whitelist | List of event kinds to always allow. Leave empty to allow any. |
99-
| limits.event.kind.blacklist | List of event kinds to always reject. Leave empty to allow any. |
100-
| limits.event.pubkey.minLeadingZeroBits | Leading zero bits required on the public key of incoming events for proof of work. |
101-
| | Defaults to zero. Disabled when set to zero. |
102-
| limits.event.pubkey.whitelist | List of public keys to always allow. Only public keys in this list will be able to post to this relay. Use for private relays. |
103-
| limits.event.pubkey.blacklist | List of public keys to always reject. Public keys in this list will not be able to post to this relay. |
104-
| limits.event.createdAt.maxPositiveDelta | Maximum number of seconds an event's `created_at` can be in the future. Defaults to 900 (15 minutes). Disabled when set to zero. |
105-
| limits.event.createdAt.minNegativeDelta | Maximum number of secodns an event's `created_at` can be in the past. Defaults to zero. Disabled when set to zero. |
73+
| info.relay_url | Public-facing URL of your relay. (e.g. wss://relay.your-domain.com) |
74+
| limits.admissionCheck.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
75+
| limits.admissionCheck.rateLimits[].period | Rate limit period in milliseconds. |
76+
| limits.admissionCheck.rateLimits[].rate | Maximum number of admission checks during period. |
77+
| limits.client.subscription.maxFilters | Maximum number of filters per subscription. Defaults to 10. Disabled when set to zero. |
78+
| limits.client.subscription.maxSubscriptions | Maximum number of subscriptions per connected client. Defaults to 10. Disabled when set to zero. |
10679
| limits.event.content[].kinds | List of event kinds to apply limit. Use `[min, max]` for ranges. Optional. |
10780
| limits.event.content[].maxLength | Maximum length of `content`. Defaults to 1 MB. Disabled when set to zero. |
81+
| limits.event.createdAt.maxPositiveDelta | Maximum number of seconds an event's `created_at` can be in the future. Defaults to 900 (15 minutes). Disabled when set to zero. |
82+
| limits.event.createdAt.minNegativeDelta | Maximum number of seconds an event's `created_at` can be in the past. Defaults to zero. Disabled when set to zero. |
83+
| limits.event.eventId.minLeadingZeroBits | Leading zero bits required on every incoming event for proof of work. Defaults to zero. Disabled when set to zero. |
84+
| limits.event.kind.blacklist | List of event kinds to always reject. Leave empty to allow any. |
85+
| limits.event.kind.whitelist | List of event kinds to always allow. Leave empty to allow any. |
86+
| limits.event.pubkey.blacklist | List of public keys to always reject. Public keys in this list will not be able to post to this relay. |
87+
| limits.event.pubkey.minLeadingZeroBits | Leading zero bits required on the public key of incoming events for proof of work. Defaults to zero. Disabled when set to zero. |
88+
| limits.event.pubkey.whitelist | List of public keys to always allow. Only public keys in this list will be able to post to this relay. Use for private relays. |
10889
| limits.event.rateLimits[].kinds | List of event kinds rate limited. Use `[min, max]` for ranges. Optional. |
10990
| limits.event.rateLimits[].period | Rate limiting period in milliseconds. |
11091
| limits.event.rateLimits[].rate | Maximum number of events during period. |
111-
| limits.event.whitelists.pubkeys | List of public keys to ignore rate limits. |
112-
| limits.event.whitelists.ipAddresses | List of IPs (IPv4 or IPv6) to ignore rate limits. |
113-
| limits.event.retention.maxDays | Maximum number of days to retain events. Purge deletes events that are expired (`expires_at`), soft-deleted (`deleted_at`), or older than this window (`created_at`). Any non-positive value disables retention purge. |
11492
| limits.event.retention.kind.whitelist | Event kinds excluded from retention purge. NIP-62 `REQUEST_TO_VANISH` is always excluded from retention purge, even if not listed here. |
93+
| limits.event.retention.maxDays | Maximum number of days to retain events. Purge deletes events that are expired (`expires_at`), soft-deleted (`deleted_at`), or older than this window (`created_at`). Any non-positive value disables retention purge. |
11594
| limits.event.retention.pubkey.whitelist | Public keys excluded from retention purge. |
116-
| limits.client.subscription.maxSubscriptions | Maximum number of subscriptions per connected client. Defaults to 10. Disabled when set to zero. |
117-
| limits.client.subscription.maxFilters | Maximum number of filters per subscription. Defaults to 10. Disabled when set to zero. |
95+
| limits.event.whitelists.ipAddresses | List of IPs (IPv4 or IPv6) to ignore rate limits. |
96+
| limits.event.whitelists.pubkeys | List of public keys to ignore rate limits. |
97+
| limits.message.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
11898
| limits.message.rateLimits[].period | Rate limit period in milliseconds. |
11999
| limits.message.rateLimits[].rate | Maximum number of messages during period. |
120-
| limits.message.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
121-
| limits.admissionCheck.rateLimits[].period | Rate limit period in milliseconds. |
122-
| limits.admissionCheck.rateLimits[].rate | Maximum number of admission checks during period. |
123-
| limits.admissionCheck.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
100+
| mirroring.static[].address | Address of mirrored relay. (e.g. ws://100.100.100.100:8008) |
101+
| mirroring.static[].filters | Subscription filters used to mirror. |
102+
| mirroring.static[].limits.event | Event limit overrides for this mirror. See configurations under limits.event. |
103+
| mirroring.static[].secret | Secret to pass to relays. Nostream relays only. Optional. |
104+
| mirroring.static[].skipAdmissionCheck | Disable the admission fee check for events coming from this mirror. |
105+
| network.maxPayloadSize | Maximum number of bytes accepted per WebSocket frame |
106+
| network.remoteIpHeader | HTTP header from proxy containing IP address from client. |
107+
| network.trustedProxies | Optional allow-list of proxy IPs allowed to set `network.remoteIpHeader`; otherwise socket remote IP is used. |
108+
| paymentProcessors.lnbits.baseURL | Base URL of your Lnbits instance. |
109+
| paymentProcessors.lnbits.callbackBaseURL | Public-facing Nostream's Lnbits Callback URL. (e.g. https://relay.your-domain.com/callbacks/lnbits) |
110+
| paymentProcessors.lnurl.invoiceURL | [LUD-06 Pay Request](https://github.com/lnurl/luds/blob/luds/06.md) provider URL. (e.g. https://getalby.com/lnurlp/your-username) |
111+
| paymentProcessors.zebedee.baseURL | Zebedee's API base URL. |
112+
| paymentProcessors.zebedee.callbackBaseURL | Public-facing Nostream's Zebedee Callback URL (e.g. https://relay.your-domain.com/callbacks/zebedee) |
113+
| paymentProcessors.zebedee.ipWhitelist | List with Zebedee's API Production IPs. See [ZBD API Documentation](https://api-reference.zebedee.io/#c7e18276-6935-4cca-89ae-ad949efe9a6a) for more info. |
114+
| payments.enabled | Enabled payments. Defaults to false. |
115+
| payments.feeSchedules.admission[].amount | Admission fee amount in msats. |
116+
| payments.feeSchedules.admission[].enabled | Enables admission fee. Defaults to false. |
117+
| payments.feeSchedules.admission[].whitelists.event_kinds | List of event kinds to waive admission fee. Use `[min, max]` for ranges. |
118+
| payments.feeSchedules.admission[].whitelists.pubkeys | List of pubkeys to waive admission fee. |
119+
| payments.processor | Either `zebedee`, `lnbits`, `lnurl`. |
120+
| workers.count | Number of workers to spin up to handle incoming connections. Spin workers as many CPUs are available when set to zero. Defaults to zero. |

resources/default-settings.yaml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,16 @@ paymentsProcessors:
3737
callbackBaseURL: https://nostream.your-domain.com/callbacks/opennode
3838
network:
3939
maxPayloadSize: 524288
40-
# Comment the next line if using CloudFlare proxy
41-
remoteIpHeader: x-forwarded-for
42-
# Optional: only trust forwarding headers from these proxy IPs
43-
trustedProxies: []
44-
# Uncomment the next line if using CloudFlare proxy
40+
# Uncomment only when using a trusted reverse proxy and configuring trustedProxies.
41+
# remoteIpHeader: x-forwarded-for
4542
# remoteIpHeader: cf-connecting-ip
43+
# Proxy IPs allowed to set remoteIpHeader (loopback and common docker internal)
44+
trustedProxies:
45+
- "127.0.0.1"
46+
- "::ffff:127.0.0.1"
47+
- "::1"
48+
- "10.10.10.1"
49+
- "::ffff:10.10.10.1"
4650
workers:
4751
count: 0
4852
mirroring:

src/utils/http.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { IncomingMessage } from 'http'
22

33
import { Settings } from '../@types/settings'
44

5+
let warnedEmptyTrustedProxies = false
6+
7+
export const _resetWarnings = (): void => { warnedEmptyTrustedProxies = false }
8+
59
const normalizeIpAddress = (input: string): string => {
610
if (input.startsWith('::ffff:')) {
711
return input.slice(7)
@@ -36,13 +40,13 @@ export const getRemoteAddress = (request: IncomingMessage, settings: Settings):
3640
}
3741

3842
const trustedProxies = settings.network?.trustedProxies
39-
if (header && (!Array.isArray(trustedProxies) || trustedProxies.length === 0)) {
43+
if (header && (!Array.isArray(trustedProxies) || trustedProxies.length === 0) && !warnedEmptyTrustedProxies) {
4044
console.warn('WARNING: network.remoteIpHeader is set but network.trustedProxies is empty. Forwarded headers will be ignored. Add your proxy IP to network.trustedProxies.')
45+
warnedEmptyTrustedProxies = true
4146
}
4247

43-
const headerAddress = header
44-
? request.headers[header]
45-
: undefined
48+
const rawHeaderAddress = header ? request.headers[header] : undefined
49+
const headerAddress = Array.isArray(rawHeaderAddress) ? rawHeaderAddress[0] : rawHeaderAddress
4650
const socketAddress = request.socket.remoteAddress
4751

4852
const trustedProxy = typeof socketAddress === 'string'

test/unit/utils/http.spec.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { expect } from 'chai'
22
import { IncomingMessage } from 'http'
3+
import sinon from 'sinon'
34

4-
import { getRemoteAddress } from '../../../src/utils/http'
5+
import { _resetWarnings, getRemoteAddress } from '../../../src/utils/http'
56

67
describe('getRemoteAddress', () => {
78
const header = 'x-forwarded-for'
@@ -72,4 +73,28 @@ describe('getRemoteAddress', () => {
7273
)
7374
).to.equal(socketAddress)
7475
})
76+
77+
it('returns first address when forwarded header is an array', () => {
78+
const arrayRequest = {
79+
headers: { [header]: [address, 'other-address'] },
80+
socket: { remoteAddress: socketAddress },
81+
} as any
82+
expect(
83+
getRemoteAddress(
84+
arrayRequest,
85+
{ network: { remoteIpHeader: header, trustedProxies: [socketAddress] } } as any,
86+
)
87+
).to.equal(address)
88+
})
89+
90+
it('emits empty trustedProxies warning only once', () => {
91+
_resetWarnings()
92+
const warn = sinon.stub(console, 'warn')
93+
const settings = { network: { remoteIpHeader: header, trustedProxies: [] } } as any
94+
getRemoteAddress(request, settings)
95+
getRemoteAddress(request, settings)
96+
const warningCalls = warn.args.filter(([msg]) => typeof msg === 'string' && msg.includes('trustedProxies is empty'))
97+
warn.restore()
98+
expect(warningCalls).to.have.lengthOf(1)
99+
})
75100
})

0 commit comments

Comments
 (0)