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.
This commit is contained in:
@@ -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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
SubscriptionEnabled bool `env:"ORLY_SUBSCRIPTION_ENABLED" default:"false" usage:"enable subscription-based access control requiring payment for non-directory events"`
|
||||||
|
|||||||
@@ -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)) })
|
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 is active, auth is required, or AuthToWrite is enabled, send a challenge (same as REQ path)
|
||||||
if acl.Registry.Active.Load() != "none" {
|
if acl.Registry.Active.Load() != "none" || l.Config.AuthRequired || l.Config.AuthToWrite {
|
||||||
if err = authenvelope.NewChallengeWith(l.challenge.Load()).Write(l); chk.E(err) {
|
if err = authenvelope.NewChallengeWith(l.challenge.Load()).Write(l); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -36,11 +36,31 @@ func (l *Listener) HandleCount(msg []byte) (err error) {
|
|||||||
|
|
||||||
// Check read permissions
|
// Check read permissions
|
||||||
accessLevel := acl.Registry.GetAccessLevel(l.authedPubkey.Load(), l.remote)
|
accessLevel := acl.Registry.GetAccessLevel(l.authedPubkey.Load(), l.remote)
|
||||||
switch accessLevel {
|
|
||||||
case "none":
|
// If auth is required but user is not authenticated, deny access
|
||||||
return errors.New("auth required: user not authed or has no read access")
|
if l.Config.AuthRequired && len(l.authedPubkey.Load()) == 0 {
|
||||||
default:
|
return errors.New("authentication required")
|
||||||
// allowed to read
|
}
|
||||||
|
|
||||||
|
// 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
|
// Use a bounded context for counting, isolated from the connection context
|
||||||
|
|||||||
@@ -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
|
// 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
|
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
|
pubkeyForACL = env.E.Pubkey
|
||||||
log.I.F(
|
log.I.F(
|
||||||
"HandleEvent: ACL mode is 'none' and auth not required, using event pubkey for ACL check: %s",
|
"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()
|
pubkeyForACL = l.authedPubkey.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If auth is required but user is not authenticated, deny access
|
// If auth is required or AuthToWrite is enabled but user is not authenticated, deny access
|
||||||
if l.Config.AuthRequired && len(l.authedPubkey.Load()) == 0 {
|
if (l.Config.AuthRequired || l.Config.AuthToWrite) && len(l.authedPubkey.Load()) == 0 {
|
||||||
log.D.F("HandleEvent: authentication required but user not authenticated")
|
log.D.F("HandleEvent: authentication required for write operations but user not authenticated")
|
||||||
if err = okenvelope.NewFrom(
|
if err = okenvelope.NewFrom(
|
||||||
env.Id(), false,
|
env.Id(), false,
|
||||||
reason.AuthRequired.F("authentication required"),
|
reason.AuthRequired.F("authentication required for write operations"),
|
||||||
).Write(l); chk.E(err) {
|
).Write(l); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// 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 {
|
if acl.Registry.Active.Load() != "none" || l.Config.AuthRequired || l.Config.AuthToWrite {
|
||||||
if err = authenvelope.NewChallengeWith(l.challenge.Load()).
|
if err = authenvelope.NewChallengeWith(l.challenge.Load()).
|
||||||
Write(l); chk.E(err) {
|
Write(l); chk.E(err) {
|
||||||
return
|
return
|
||||||
@@ -72,18 +72,41 @@ func (l *Listener) HandleReq(msg []byte) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch accessLevel {
|
// If AuthToWrite is enabled, allow REQ without auth (but still check ACL)
|
||||||
case "none":
|
// Skip the auth requirement check for REQ when AuthToWrite is true
|
||||||
// For REQ denial, send a CLOSED with auth-required reason (NIP-01)
|
if l.Config.AuthToWrite && len(l.authedPubkey.Load()) == 0 {
|
||||||
if err = closedenvelope.NewFrom(
|
// Allow unauthenticated REQ when AuthToWrite is enabled
|
||||||
env.Subscription,
|
// but still respect ACL access levels if ACL is active
|
||||||
reason.AuthRequired.F("user not authed or has no read access"),
|
if acl.Registry.Active.Load() != "none" {
|
||||||
).Write(l); chk.E(err) {
|
switch accessLevel {
|
||||||
return
|
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
|
var events event.S
|
||||||
// Create a single context for all filter queries, isolated from the connection context
|
// 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
|
cancel = false
|
||||||
subbedFilters = append(subbedFilters, f)
|
subbedFilters = append(subbedFilters, f)
|
||||||
} else {
|
} 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
|
var notFounds [][]byte
|
||||||
for _, id := range f.Ids.T {
|
for _, id := range f.Ids.T {
|
||||||
if _, ok := seen[hexenc.Enc(id)]; ok {
|
if _, ok := seen[hexenc.Enc(id)]; ok {
|
||||||
@@ -574,7 +598,7 @@ func (l *Listener) HandleReq(msg []byte) (err error) {
|
|||||||
remote: l.remote,
|
remote: l.remote,
|
||||||
Id: string(env.Subscription),
|
Id: string(env.Subscription),
|
||||||
Receiver: receiver,
|
Receiver: receiver,
|
||||||
Filters: env.Filters,
|
Filters: &subbedFilters,
|
||||||
AuthedPubkey: l.authedPubkey.Load(),
|
AuthedPubkey: l.authedPubkey.Load(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
v0.17.17
|
v0.17.18
|
||||||
Reference in New Issue
Block a user