Skip to content

Commit 344f64e

Browse files
authored
Merge branch 'main' into feat/uppercase-tag-filters
2 parents 1457a6b + 7edd6c3 commit 344f64e

22 files changed

Lines changed: 714 additions & 96 deletions

.changeset/full-donuts-allow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"nostream": minor
3+
---
4+
5+
added NIP-45 COUNT support with end-to-end handling (validation, handler routing, DB counting, and tests).

.changeset/gentle-dots-swim.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
docs: add NIP-62 to supported feature list
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

.changeset/tasty-parents-pump.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"nostream": patch
3+
---
4+
5+
Fix IP spoofing via unconditional trust of x-forwarded-for header

CONFIGURATION.md

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,12 @@ The settings below are listed in alphabetical order by name. Please keep this ta
140140
| limits.event.content[].kinds | List of event kinds to apply limit. Use `[min, max]` for ranges. Optional. |
141141
| limits.event.content[].maxLength | Maximum length of `content`. Defaults to 1 MB. Disabled when set to zero. |
142142
| 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. |
143-
| 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. |
144-
| limits.event.eventId.minLeadingZeroBits | Leading zero bits required on every incoming event for proof of work. |
145-
| | Defaults to zero. Disabled when set to zero. |
143+
| 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. |
144+
| limits.event.eventId.minLeadingZeroBits | Leading zero bits required on every incoming event for proof of work. Defaults to zero. Disabled when set to zero. |
146145
| limits.event.kind.blacklist | List of event kinds to always reject. Leave empty to allow any. |
147146
| limits.event.kind.whitelist | List of event kinds to always allow. Leave empty to allow any. |
148147
| 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. |
149-
| limits.event.pubkey.minLeadingZeroBits | Leading zero bits required on the public key of incoming events for proof of work. |
150-
| | Defaults to zero. Disabled when set to zero. |
148+
| 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. |
151149
| 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. |
152150
| limits.event.rateLimits[].kinds | List of event kinds rate limited. Use `[min, max]` for ranges. Optional. |
153151
| limits.event.rateLimits[].period | Rate limiting period in milliseconds. For `sliding_window`: the time window during which requests are counted. For `ewma`: the half-life of the exponential decay — shorter values forget bursts faster, longer values are stricter on bursty clients. |
@@ -176,6 +174,7 @@ The settings below are listed in alphabetical order by name. Please keep this ta
176174
| nip05.mode | NIP-05 verification mode: `enabled` requires verification, `passive` verifies without blocking, `disabled` does nothing. Defaults to `disabled`. |
177175
| nip05.verifyExpiration | Time in milliseconds before a successful NIP-05 verification expires and needs re-checking. Defaults to 604800000 (1 week). |
178176
| nip05.verifyUpdateFrequency | Minimum interval in milliseconds between re-verification attempts for a given author. Defaults to 86400000 (24 hours). |
177+
| nip45.enabled | Enable or disable NIP-45 COUNT handling. Defaults to true. |
179178
| paymentProcessors.lnbits.baseURL | Base URL of your Lnbits instance. |
180179
| paymentProcessors.lnbits.callbackBaseURL | Public-facing Nostream's Lnbits Callback URL. (e.g. https://relay.your-domain.com/callbacks/lnbits) |
181180
| 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) |
@@ -195,3 +194,30 @@ The settings below are listed in alphabetical order by name. Please keep this ta
195194
| limits.admissionCheck.rateLimits[].rate | Maximum number of admission checks during period. |
196195
| limits.admissionCheck.ipWhitelist | List of IPs (IPv4 or IPv6) to ignore rate limits. |
197196
| limits.rateLimiter.strategy | Rate limiting strategy. Either `ewma` or `sliding_window`. Defaults to `ewma`. When using `ewma`, the `period` field in each rate limit serves as the half-life for the exponential decay function. Note: when switching from `sliding_window` to `ewma`, consider increasing `rate` values slightly as EWMA penalizes bursty behavior more aggressively. |
197+
| mirroring.static[].address | Address of mirrored relay. (e.g. ws://100.100.100.100:8008) |
198+
| mirroring.static[].filters | Subscription filters used to mirror. |
199+
| mirroring.static[].limits.event | Event limit overrides for this mirror. See configurations under limits.event. |
200+
| mirroring.static[].secret | Secret to pass to relays. Nostream relays only. Optional. |
201+
| mirroring.static[].skipAdmissionCheck | Disable the admission fee check for events coming from this mirror. |
202+
| network.maxPayloadSize | Maximum number of bytes accepted per WebSocket frame |
203+
| network.remoteIpHeader | HTTP header from proxy containing IP address from client. |
204+
| network.trustedProxies | Optional allow-list of proxy IPs allowed to set `network.remoteIpHeader`; otherwise socket remote IP is used. |
205+
| nip05.domainBlacklist | List of domains blocked from NIP-05 verification. Authors with NIP-05 at these domains will be rejected. |
206+
| nip05.domainWhitelist | List of domains allowed for NIP-05 verification. If set, only authors verified at these domains can publish. |
207+
| nip05.maxConsecutiveFailures | Number of consecutive verification failures before giving up on an author. Defaults to 20. |
208+
| nip05.mode | NIP-05 verification mode: `enabled` requires verification, `passive` verifies without blocking, `disabled` does nothing. Defaults to `disabled`. |
209+
| nip05.verifyExpiration | Time in milliseconds before a successful NIP-05 verification expires and needs re-checking. Defaults to 604800000 (1 week). |
210+
| nip05.verifyUpdateFrequency | Minimum interval in milliseconds between re-verification attempts for a given author. Defaults to 86400000 (24 hours). |
211+
| paymentProcessors.lnbits.baseURL | Base URL of your Lnbits instance. |
212+
| paymentProcessors.lnbits.callbackBaseURL | Public-facing Nostream's Lnbits Callback URL. (e.g. https://relay.your-domain.com/callbacks/lnbits) |
213+
| 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) |
214+
| paymentProcessors.zebedee.baseURL | Zebedee's API base URL. |
215+
| paymentProcessors.zebedee.callbackBaseURL | Public-facing Nostream's Zebedee Callback URL (e.g. https://relay.your-domain.com/callbacks/zebedee) |
216+
| 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. |
217+
| payments.enabled | Enabled payments. Defaults to false. |
218+
| payments.feeSchedules.admission[].amount | Admission fee amount in msats. |
219+
| payments.feeSchedules.admission[].enabled | Enables admission fee. Defaults to false. |
220+
| payments.feeSchedules.admission[].whitelists.event_kinds | List of event kinds to waive admission fee. Use `[min, max]` for ranges. |
221+
| payments.feeSchedules.admission[].whitelists.pubkeys | List of pubkeys to waive admission fee. |
222+
| payments.processor | Either `zebedee`, `lnbits`, `lnurl`. |
223+
| 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. |

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,8 @@
2121
<a href="https://github.com/cameri/nostream/blob/main/LICENSE">
2222
<img alt="GitHub license" src="https://img.shields.io/github/license/Cameri/nostream" />
2323
</a>
24-
<a href='https://coveralls.io/github/Cameri/nostream?branch=main'>
25-
<img alt='Coverage Status' src='https://coveralls.io/repos/github/Cameri/nostream/badge.svg?branch=main' />
26-
</a>
27-
<a href='https://sonarcloud.io/project/overview?id=Cameri_nostr-ts-relay'>
28-
<img alt='Sonarcloud quality gate' src='https://sonarcloud.io/api/project_badges/measure?project=Cameri_nostr&metric=alert_status' />
24+
<a href='https://coveralls.io/github/cameri/nostream?branch=main'>
25+
<img alt='Coverage Status' src='https://coveralls.io/repos/github/cameri/nostream/badge.svg?branch=main' />
2926
</a>
3027
<a href='https://github.com/cameri/nostream/actions'>
3128
<img alt='Build status' src='https://github.com/cameri/nostream/actions/workflows/checks.yml/badge.svg?branch=main&event=push' />
@@ -64,6 +61,8 @@ NIPs with a relay-specific implementation are listed here.
6461
- [x] NIP-33: Parameterized Replaceable Events
6562
- [x] NIP-40: Expiration Timestamp
6663
- [x] NIP-44: Encrypted Payloads (Versioned)
64+
- [x] NIP-45: Event Counts
65+
- [x] NIP-62: Request to Vanish
6766

6867
## Requirements
6968

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
28,
1919
33,
2020
40,
21-
44
21+
44,
22+
45
2223
],
2324
"supportedNipExtensions": [
2425
"11a"

resources/default-settings.yaml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,20 @@ nip05:
5353
domainWhitelist: []
5454
# Block authors with NIP-05 at these domains
5555
domainBlacklist: []
56+
nip45:
57+
enabled: true
5658
network:
5759
maxPayloadSize: 524288
58-
# Comment the next line if using CloudFlare proxy
59-
remoteIpHeader: x-forwarded-for
60-
# Uncomment the next line if using CloudFlare proxy
60+
# Uncomment only when using a trusted reverse proxy and configuring trustedProxies.
61+
# remoteIpHeader: x-forwarded-for
6162
# remoteIpHeader: cf-connecting-ip
63+
# Proxy IPs allowed to set remoteIpHeader (loopback and common docker internal)
64+
trustedProxies:
65+
- "127.0.0.1"
66+
- "::ffff:127.0.0.1"
67+
- "::1"
68+
- "10.10.10.1"
69+
- "::ffff:10.10.10.1"
6270
workers:
6371
count: 0
6472
mirroring:

src/@types/messages.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ export enum MessageType {
1010
NOTICE = 'NOTICE',
1111
EOSE = 'EOSE',
1212
OK = 'OK',
13+
COUNT = 'COUNT',
14+
CLOSED = 'CLOSED',
1315
}
1416

15-
export type IncomingMessage = (SubscribeMessage | IncomingEventMessage | UnsubscribeMessage) & {
17+
export type IncomingMessage = (SubscribeMessage | IncomingEventMessage | UnsubscribeMessage | CountMessage) & {
1618
[ContextMetadataKey]?: ContextMetadata
1719
}
1820

19-
export type OutgoingMessage = OutgoingEventMessage | EndOfStoredEventsNotice | NoticeMessage | CommandResult
21+
export type OutgoingMessage = OutgoingEventMessage | EndOfStoredEventsNotice | NoticeMessage | CommandResult | CountResultMessage | ClosedMessage
2022

2123
export type SubscribeMessage = {
2224
[index in Range<2, 100>]: SubscriptionFilter
@@ -25,6 +27,13 @@ export type SubscribeMessage = {
2527
1: SubscriptionId
2628
} & Array<SubscriptionFilter>
2729

30+
export type CountMessage = {
31+
[index in Range<2, 100>]: SubscriptionFilter
32+
} & {
33+
0: MessageType.COUNT
34+
1: SubscriptionId
35+
} & Array<SubscriptionFilter>
36+
2837
export type IncomingEventMessage = EventMessage & [MessageType.EVENT, Event]
2938

3039
export type IncomingRelayedEventMessage = [MessageType.EVENT, RelayedEvent, Secret]
@@ -62,3 +71,21 @@ export interface EndOfStoredEventsNotice {
6271
0: MessageType.EOSE
6372
1: SubscriptionId
6473
}
74+
75+
export interface CountResultPayload {
76+
count: number
77+
approximate?: boolean
78+
hll?: string
79+
}
80+
81+
export interface CountResultMessage {
82+
0: MessageType.COUNT
83+
1: SubscriptionId
84+
2: CountResultPayload
85+
}
86+
87+
export interface ClosedMessage {
88+
0: MessageType.CLOSED
89+
1: SubscriptionId
90+
2: string
91+
}

src/@types/repositories.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export interface IEventRepository {
3333
upsert(event: Event): Promise<number>
3434
upsertMany(events: Event[]): Promise<number>
3535
findByFilters(filters: SubscriptionFilter[]): IQueryResult<DBEvent[]>
36+
countByFilters(filters: SubscriptionFilter[]): Promise<number>
3637
deleteByPubkeyAndIds(pubkey: Pubkey, ids: EventId[]): Promise<number>
3738
deleteByPubkeyExceptKinds(pubkey: Pubkey, excludedKinds: number[]): Promise<number>
3839
hasActiveRequestToVanish(pubkey: Pubkey): Promise<boolean>

0 commit comments

Comments
 (0)