From 75f2f379ec94d9215bb4097ebbc84fdc0ac5f3c2 Mon Sep 17 00:00:00 2001 From: mleku Date: Fri, 24 Oct 2025 20:16:03 +0100 Subject: [PATCH] Enhance authentication handling in request processing - Updated HandleCount, HandleEvent, and HandleReq functions to improve authentication checks based on new configuration options. - Introduced `AuthToWrite` configuration to allow unauthenticated access for COUNT and REQ operations while still enforcing ACL checks. - Enhanced comments for clarity on authentication requirements and access control logic. - Bumped version to v0.17.18. --- app/config/config.go | 1 + app/handle-count.go | 34 ++++++++++++++++++++++------ app/handle-event.go | 12 +++++----- app/handle-req.go | 54 ++++++++++++++++++++++++++++++++------------ pkg/version/version | 2 +- 5 files changed, 74 insertions(+), 29 deletions(-) diff --git a/app/config/config.go b/app/config/config.go index d4780d1..2107ba2 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -44,6 +44,7 @@ type C struct { Owners []string `env:"ORLY_OWNERS" usage:"comma-separated list of owner npubs, who have full control of the relay for wipe and restart and other functions"` ACLMode string `env:"ORLY_ACL_MODE" usage:"ACL mode: follows, managed (nip-86), none" default:"none"` AuthRequired bool `env:"ORLY_AUTH_REQUIRED" usage:"require authentication for all requests (works with managed ACL)" default:"false"` + AuthToWrite bool `env:"ORLY_AUTH_TO_WRITE" usage:"require authentication only for write operations (EVENT), allow REQ/COUNT without auth" default:"false"` BootstrapRelays []string `env:"ORLY_BOOTSTRAP_RELAYS" usage:"comma-separated list of bootstrap relay URLs for initial sync"` NWCUri string `env:"ORLY_NWC_URI" usage:"NWC (Nostr Wallet Connect) connection string for Lightning payments"` SubscriptionEnabled bool `env:"ORLY_SUBSCRIPTION_ENABLED" default:"false" usage:"enable subscription-based access control requiring payment for non-directory events"` diff --git a/app/handle-count.go b/app/handle-count.go index bddab1c..ff17402 100644 --- a/app/handle-count.go +++ b/app/handle-count.go @@ -27,8 +27,8 @@ func (l *Listener) HandleCount(msg []byte) (err error) { } log.D.C(func() string { return fmt.Sprintf("COUNT sub=%s filters=%d", env.Subscription, len(env.Filters)) }) - // If ACL is active, send a challenge (same as REQ path) - if acl.Registry.Active.Load() != "none" { + // If ACL is active, auth is required, or AuthToWrite is enabled, send a challenge (same as REQ path) + if acl.Registry.Active.Load() != "none" || l.Config.AuthRequired || l.Config.AuthToWrite { if err = authenvelope.NewChallengeWith(l.challenge.Load()).Write(l); chk.E(err) { return } @@ -36,11 +36,31 @@ func (l *Listener) HandleCount(msg []byte) (err error) { // Check read permissions accessLevel := acl.Registry.GetAccessLevel(l.authedPubkey.Load(), l.remote) - switch accessLevel { - case "none": - return errors.New("auth required: user not authed or has no read access") - default: - // allowed to read + + // If auth is required but user is not authenticated, deny access + if l.Config.AuthRequired && len(l.authedPubkey.Load()) == 0 { + return errors.New("authentication required") + } + + // If AuthToWrite is enabled, allow COUNT without auth (but still check ACL) + if l.Config.AuthToWrite && len(l.authedPubkey.Load()) == 0 { + // Allow unauthenticated COUNT when AuthToWrite is enabled + // but still respect ACL access levels if ACL is active + if acl.Registry.Active.Load() != "none" { + switch accessLevel { + case "none", "blocked", "banned": + return errors.New("auth required: user not authed or has no read access") + } + } + // Allow the request to proceed without authentication + } else { + // Only check ACL access level if not already handled by AuthToWrite + switch accessLevel { + case "none": + return errors.New("auth required: user not authed or has no read access") + default: + // allowed to read + } } // Use a bounded context for counting, isolated from the connection context diff --git a/app/handle-event.go b/app/handle-event.go index b14377b..ea1a27d 100644 --- a/app/handle-event.go +++ b/app/handle-event.go @@ -203,9 +203,9 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { ) // If ACL mode is "none" and no pubkey is set, use the event's pubkey - // But if auth is required, always use the authenticated pubkey + // But if auth is required or AuthToWrite is enabled, always use the authenticated pubkey var pubkeyForACL []byte - if len(l.authedPubkey.Load()) == 0 && acl.Registry.Active.Load() == "none" && !l.Config.AuthRequired { + if len(l.authedPubkey.Load()) == 0 && acl.Registry.Active.Load() == "none" && !l.Config.AuthRequired && !l.Config.AuthToWrite { pubkeyForACL = env.E.Pubkey log.I.F( "HandleEvent: ACL mode is 'none' and auth not required, using event pubkey for ACL check: %s", @@ -215,12 +215,12 @@ func (l *Listener) HandleEvent(msg []byte) (err error) { pubkeyForACL = l.authedPubkey.Load() } - // If auth is required but user is not authenticated, deny access - if l.Config.AuthRequired && len(l.authedPubkey.Load()) == 0 { - log.D.F("HandleEvent: authentication required but user not authenticated") + // If auth is required or AuthToWrite is enabled but user is not authenticated, deny access + if (l.Config.AuthRequired || l.Config.AuthToWrite) && len(l.authedPubkey.Load()) == 0 { + log.D.F("HandleEvent: authentication required for write operations but user not authenticated") if err = okenvelope.NewFrom( env.Id(), false, - reason.AuthRequired.F("authentication required"), + reason.AuthRequired.F("authentication required for write operations"), ).Write(l); chk.E(err) { return } diff --git a/app/handle-req.go b/app/handle-req.go index c8e1789..37a24c7 100644 --- a/app/handle-req.go +++ b/app/handle-req.go @@ -51,8 +51,8 @@ func (l *Listener) HandleReq(msg []byte) (err error) { ) }, ) - // send a challenge to the client to auth if an ACL is active or auth is required - if acl.Registry.Active.Load() != "none" || l.Config.AuthRequired { + // send a challenge to the client to auth if an ACL is active, auth is required, or AuthToWrite is enabled + if acl.Registry.Active.Load() != "none" || l.Config.AuthRequired || l.Config.AuthToWrite { if err = authenvelope.NewChallengeWith(l.challenge.Load()). Write(l); chk.E(err) { return @@ -72,18 +72,41 @@ func (l *Listener) HandleReq(msg []byte) (err error) { return } - switch accessLevel { - case "none": - // For REQ denial, send a CLOSED with auth-required reason (NIP-01) - if err = closedenvelope.NewFrom( - env.Subscription, - reason.AuthRequired.F("user not authed or has no read access"), - ).Write(l); chk.E(err) { - return + // If AuthToWrite is enabled, allow REQ without auth (but still check ACL) + // Skip the auth requirement check for REQ when AuthToWrite is true + if l.Config.AuthToWrite && len(l.authedPubkey.Load()) == 0 { + // Allow unauthenticated REQ when AuthToWrite is enabled + // but still respect ACL access levels if ACL is active + if acl.Registry.Active.Load() != "none" { + switch accessLevel { + case "none", "blocked", "banned": + if err = closedenvelope.NewFrom( + env.Subscription, + reason.AuthRequired.F("user not authed or has no read access"), + ).Write(l); chk.E(err) { + return + } + return + } + } + // Allow the request to proceed without authentication + } + + // Only check ACL access level if not already handled by AuthToWrite + if !l.Config.AuthToWrite || len(l.authedPubkey.Load()) > 0 { + switch accessLevel { + case "none": + // For REQ denial, send a CLOSED with auth-required reason (NIP-01) + if err = closedenvelope.NewFrom( + env.Subscription, + reason.AuthRequired.F("user not authed or has no read access"), + ).Write(l); chk.E(err) { + return + } + return + default: + // user has read access or better, continue } - return - default: - // user has read access or better, continue } var events event.S // Create a single context for all filter queries, isolated from the connection context @@ -537,7 +560,8 @@ func (l *Listener) HandleReq(msg []byte) (err error) { cancel = false subbedFilters = append(subbedFilters, f) } else { - // remove the IDs that we already sent + // remove the IDs that we already sent, as it's one less + // comparison we have to make. var notFounds [][]byte for _, id := range f.Ids.T { if _, ok := seen[hexenc.Enc(id)]; ok { @@ -574,7 +598,7 @@ func (l *Listener) HandleReq(msg []byte) (err error) { remote: l.remote, Id: string(env.Subscription), Receiver: receiver, - Filters: env.Filters, + Filters: &subbedFilters, AuthedPubkey: l.authedPubkey.Load(), }, ) diff --git a/pkg/version/version b/pkg/version/version index 544d547..a2278de 100644 --- a/pkg/version/version +++ b/pkg/version/version @@ -1 +1 @@ -v0.17.17 \ No newline at end of file +v0.17.18 \ No newline at end of file