EnableRateVisitor flag

pull/654/head
binwiederhier 2023-03-03 14:55:37 -05:00
parent ecff7258ba
commit 1c4420bca8
5 changed files with 38 additions and 6 deletions

View File

@ -63,6 +63,7 @@ var flagsServe = append(
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-signup", Aliases: []string{"enable_signup"}, EnvVars: []string{"NTFY_ENABLE_SIGNUP"}, Value: false, Usage: "allows users to sign up via the web app, or API"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-signup", Aliases: []string{"enable_signup"}, EnvVars: []string{"NTFY_ENABLE_SIGNUP"}, Value: false, Usage: "allows users to sign up via the web app, or API"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-login", Aliases: []string{"enable_login"}, EnvVars: []string{"NTFY_ENABLE_LOGIN"}, Value: false, Usage: "allows users to log in via the web app, or API"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-login", Aliases: []string{"enable_login"}, EnvVars: []string{"NTFY_ENABLE_LOGIN"}, Value: false, Usage: "allows users to log in via the web app, or API"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-reservations", Aliases: []string{"enable_reservations"}, EnvVars: []string{"NTFY_ENABLE_RESERVATIONS"}, Value: false, Usage: "allows users to reserve topics (if their tier allows it)"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-reservations", Aliases: []string{"enable_reservations"}, EnvVars: []string{"NTFY_ENABLE_RESERVATIONS"}, Value: false, Usage: "allows users to reserve topics (if their tier allows it)"}),
altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-rate-visitor", Aliases: []string{"enable_rate_visitor"}, EnvVars: []string{"NTFY_ENABLE_RATE_VISITOR"}, Value: false, Usage: "enables subscriber-based rate limiting for UnifiedPush topics"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "upstream-base-url", Aliases: []string{"upstream_base_url"}, EnvVars: []string{"NTFY_UPSTREAM_BASE_URL"}, Value: "", Usage: "forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "upstream-base-url", Aliases: []string{"upstream_base_url"}, EnvVars: []string{"NTFY_UPSTREAM_BASE_URL"}, Value: "", Usage: "forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", Aliases: []string{"smtp_sender_addr"}, EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", Aliases: []string{"smtp_sender_addr"}, EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}),
altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}),
@ -139,6 +140,7 @@ func execServe(c *cli.Context) error {
enableSignup := c.Bool("enable-signup") enableSignup := c.Bool("enable-signup")
enableLogin := c.Bool("enable-login") enableLogin := c.Bool("enable-login")
enableReservations := c.Bool("enable-reservations") enableReservations := c.Bool("enable-reservations")
enableRateVisitor := c.Bool("enable-rate-visitor")
upstreamBaseURL := c.String("upstream-base-url") upstreamBaseURL := c.String("upstream-base-url")
smtpSenderAddr := c.String("smtp-sender-addr") smtpSenderAddr := c.String("smtp-sender-addr")
smtpSenderUser := c.String("smtp-sender-user") smtpSenderUser := c.String("smtp-sender-user")
@ -312,6 +314,7 @@ func execServe(c *cli.Context) error {
conf.EnableSignup = enableSignup conf.EnableSignup = enableSignup
conf.EnableLogin = enableLogin conf.EnableLogin = enableLogin
conf.EnableReservations = enableReservations conf.EnableReservations = enableReservations
conf.EnableRateVisitor = enableRateVisitor
conf.Version = c.App.Version conf.Version = c.App.Version
// Set up hot-reloading of config // Set up hot-reloading of config

View File

@ -133,6 +133,7 @@ type Config struct {
EnableSignup bool // Enable creation of accounts via API and UI EnableSignup bool // Enable creation of accounts via API and UI
EnableLogin bool EnableLogin bool
EnableReservations bool // Allow users with role "user" to own/reserve topics EnableReservations bool // Allow users with role "user" to own/reserve topics
EnableRateVisitor bool // Enable subscriber-based rate limiting for UnifiedPush topics
AccessControlAllowOrigin string // CORS header field to restrict access from web clients AccessControlAllowOrigin string // CORS header field to restrict access from web clients
Version string // injected by App Version string // injected by App
} }

View File

@ -597,7 +597,7 @@ func (s *Server) handlePublishWithoutResponse(r *http.Request, v *visitor) (*mes
if e != nil { if e != nil {
return nil, e.With(t) return nil, e.With(t)
} }
if unifiedpush && t.RateVisitor() == nil { if unifiedpush && s.config.EnableRateVisitor && t.RateVisitor() == nil {
// UnifiedPush clients must subscribe before publishing to allow proper subscriber-based rate limiting (see // UnifiedPush clients must subscribe before publishing to allow proper subscriber-based rate limiting (see
// Rate-Topics header). The 5xx response is because some app servers (in particular Mastodon) will remove // Rate-Topics header). The 5xx response is because some app servers (in particular Mastodon) will remove
// the subscription as invalid if any 400-499 code (except 429/408) is returned. // the subscription as invalid if any 400-499 code (except 429/408) is returned.
@ -1188,14 +1188,19 @@ func parseSubscribeParams(r *http.Request) (poll bool, since sinceMarker, schedu
// maybeSetRateVisitors sets the rate visitor on a topic (v.SetRateVisitor), indicating that all messages published // maybeSetRateVisitors sets the rate visitor on a topic (v.SetRateVisitor), indicating that all messages published
// to that topic will be rate limited against the rate visitor instead of the publishing visitor. // to that topic will be rate limited against the rate visitor instead of the publishing visitor.
// //
// Setting the rate visitor is ony allowed if // Setting the rate visitor is ony allowed if the `enable-rate-visitor` setting is enabled, AND
// - auth-file is not set (everything is open by default) // - auth-file is not set (everything is open by default)
// - the topic is reserved, and v.user is the owner // - or the topic is reserved, and v.user is the owner
// - the topic is not reserved, and v.user has write access // - or the topic is not reserved, and v.user has write access
// //
// Note: This TEMPORARILY also registers all topics starting with "up" (= UnifiedPush). This is to ease the transition // Note: This TEMPORARILY also registers all topics starting with "up" (= UnifiedPush). This is to ease the transition
// until the Android app will send the "Rate-Topics" header. // until the Android app will send the "Rate-Topics" header.
func (s *Server) maybeSetRateVisitors(r *http.Request, v *visitor, topics []*topic, rateTopics []string) error { func (s *Server) maybeSetRateVisitors(r *http.Request, v *visitor, topics []*topic, rateTopics []string) error {
// Bail out if not enabled
if !s.config.EnableRateVisitor {
return nil
}
// Make a list of topics that we'll actually set the RateVisitor on // Make a list of topics that we'll actually set the RateVisitor on
eligibleRateTopics := make([]*topic, 0) eligibleRateTopics := make([]*topic, 0)
for _, t := range topics { for _, t := range topics {

View File

@ -235,6 +235,20 @@
# visitor-attachment-total-size-limit: "100M" # visitor-attachment-total-size-limit: "100M"
# visitor-attachment-daily-bandwidth-limit: "500M" # visitor-attachment-daily-bandwidth-limit: "500M"
# Rate limiting: Enable subscriber-based rate limiting (mostly used for UnifiedPush)
#
# If enabled, subscribers may opt to have published messages counted against their own rate limits, as opposed
# to the publisher's rate limits. This is especially useful to increase the amount of messages that UnifiedPush
# publishers (e.g. Matrix/Mastodon servers) are allowed to send.
#
# Once enabled, a client may send a "Rate-Topics: <topic1>,<topic2>,..." header when subscribing to topics via
# HTTP stream, or websockets, thereby registering itself as the "rate visitor", i.e. the visitor whose rate limits
# to use when publishing on this topic.
#
# For your home server, you likely DO NOT NEED THIS setting.
#
# enable-rate-visitors: false
# Payments integration via Stripe # Payments integration via Stripe
# #
# - stripe-secret-key is the key used for the Stripe API communication. Setting this values # - stripe-secret-key is the key used for the Stripe API communication. Setting this values

View File

@ -15,6 +15,7 @@ import (
"net/netip" "net/netip"
"os" "os"
"path/filepath" "path/filepath"
"runtime/debug"
"strings" "strings"
"sync" "sync"
"testing" "testing"
@ -1291,7 +1292,9 @@ func TestServer_MatrixGateway_Push_Success(t *testing.T) {
} }
func TestServer_MatrixGateway_Push_Failure_NoSubscriber(t *testing.T) { func TestServer_MatrixGateway_Push_Failure_NoSubscriber(t *testing.T) {
s := newTestServer(t, newTestConfig(t)) c := newTestConfig(t)
c.EnableRateVisitor = true
s := newTestServer(t, c)
notification := `{"notification":{"devices":[{"pushkey":"http://127.0.0.1:12345/mytopic?up=1"}]}}` notification := `{"notification":{"devices":[{"pushkey":"http://127.0.0.1:12345/mytopic?up=1"}]}}`
response := request(t, s, "POST", "/_matrix/push/v1/notify", notification, nil) response := request(t, s, "POST", "/_matrix/push/v1/notify", notification, nil)
require.Equal(t, 507, response.Code) require.Equal(t, 507, response.Code)
@ -2029,6 +2032,7 @@ func TestServer_AnonymousUser_And_NonTierUser_Are_Same_Visitor(t *testing.T) {
func TestServer_SubscriberRateLimiting_Success(t *testing.T) { func TestServer_SubscriberRateLimiting_Success(t *testing.T) {
c := newTestConfigWithAuthFile(t) c := newTestConfigWithAuthFile(t)
c.VisitorRequestLimitBurst = 3 c.VisitorRequestLimitBurst = 3
c.EnableRateVisitor = true
s := newTestServer(t, c) s := newTestServer(t, c)
// "Register" visitor 1.2.3.4 to topic "subscriber1topic" as a rate limit visitor // "Register" visitor 1.2.3.4 to topic "subscriber1topic" as a rate limit visitor
@ -2082,6 +2086,7 @@ func TestServer_SubscriberRateLimiting_Success(t *testing.T) {
func TestServer_SubscriberRateLimiting_UP_Only(t *testing.T) { func TestServer_SubscriberRateLimiting_UP_Only(t *testing.T) {
c := newTestConfigWithAuthFile(t) c := newTestConfigWithAuthFile(t)
c.VisitorRequestLimitBurst = 3 c.VisitorRequestLimitBurst = 3
c.EnableRateVisitor = true
s := newTestServer(t, c) s := newTestServer(t, c)
// "Register" 5 different UnifiedPush visitors // "Register" 5 different UnifiedPush visitors
@ -2105,6 +2110,7 @@ func TestServer_SubscriberRateLimiting_UP_Only(t *testing.T) {
func TestServer_Matrix_SubscriberRateLimiting_UP_Only(t *testing.T) { func TestServer_Matrix_SubscriberRateLimiting_UP_Only(t *testing.T) {
c := newTestConfig(t) c := newTestConfig(t)
c.VisitorRequestLimitBurst = 3 c.VisitorRequestLimitBurst = 3
c.EnableRateVisitor = true
s := newTestServer(t, c) s := newTestServer(t, c)
// "Register" 5 different UnifiedPush visitors // "Register" 5 different UnifiedPush visitors
@ -2132,6 +2138,7 @@ func TestServer_Matrix_SubscriberRateLimiting_UP_Only(t *testing.T) {
func TestServer_SubscriberRateLimiting_VisitorExpiration(t *testing.T) { func TestServer_SubscriberRateLimiting_VisitorExpiration(t *testing.T) {
c := newTestConfig(t) c := newTestConfig(t)
c.VisitorRequestLimitBurst = 3 c.VisitorRequestLimitBurst = 3
c.EnableRateVisitor = true
s := newTestServer(t, c) s := newTestServer(t, c)
// "Register" rate visitor // "Register" rate visitor
@ -2167,6 +2174,7 @@ func TestServer_SubscriberRateLimiting_VisitorExpiration(t *testing.T) {
func TestServer_SubscriberRateLimiting_ProtectedTopics(t *testing.T) { func TestServer_SubscriberRateLimiting_ProtectedTopics(t *testing.T) {
c := newTestConfigWithAuthFile(t) c := newTestConfigWithAuthFile(t)
c.AuthDefault = user.PermissionDenyAll c.AuthDefault = user.PermissionDenyAll
c.EnableRateVisitor = true
s := newTestServer(t, c) s := newTestServer(t, c)
// Create some ACLs // Create some ACLs
@ -2214,6 +2222,7 @@ func TestServer_SubscriberRateLimiting_ProtectedTopics(t *testing.T) {
func TestServer_SubscriberRateLimiting_ProtectedTopics_WithDefaultReadWrite(t *testing.T) { func TestServer_SubscriberRateLimiting_ProtectedTopics_WithDefaultReadWrite(t *testing.T) {
c := newTestConfigWithAuthFile(t) c := newTestConfigWithAuthFile(t)
c.AuthDefault = user.PermissionReadWrite c.AuthDefault = user.PermissionReadWrite
c.EnableRateVisitor = true
s := newTestServer(t, c) s := newTestServer(t, c)
// Create some ACLs // Create some ACLs
@ -2333,5 +2342,5 @@ func waitForWithMaxWait(t *testing.T, maxWait time.Duration, f func() bool) {
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
} }
t.Fatalf("Function f did not succeed after %v", maxWait) t.Fatalf("Function f did not succeed after %v: %s", maxWait, debug.Stack())
} }