Files
ntfy/server/server_middleware_test.go
T
binwiederhier ccd4dd9d85 Build the visitor per request instead of sharing it
Requests from one IP (anonymous and untiered users) shared a single visitor
object with a mutable user, so overlapping requests could be authorized as
each other's user. The visitor is now built per request and never changed;
the limit service, keyed by visitor key, still shares the buckets and counts.
The Firebase penalty becomes a limiter kind, open subscriptions move to a
node-local gauge per key, and tier users' stats are enqueued per admitted
unit.

Fixes #1051
2026-10-29 01:28:12 +00:00

63 lines
2.1 KiB
Go

package server
import (
"net/http"
"net/http/httptest"
"net/netip"
"testing"
"github.com/stretchr/testify/require"
"heckel.io/ntfy/v2/user"
)
func TestServer_EnsureUser_AnonymousNotAuthorizedAsConcurrentUser(t *testing.T) {
// An anonymous request that overlaps with an authenticated request from the same IP must not
// see that request's user, or it could mint a token for the victim's account (#1051)
c := newTestConfigWithAuthFile(t, "")
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
require.Nil(t, s.userManager.AddUser("phil", "phil12345", user.RoleUser, false))
u, err := s.userManager.User("phil")
require.Nil(t, err)
ip := netip.MustParseAddr("9.9.9.9")
anon := s.visitor(ip, nil)
s.visitor(ip, u) // The concurrent authenticated request, made deterministic
nextCalled := false
next := func(w http.ResponseWriter, r *http.Request, v *visitor) error {
nextCalled = true
return nil
}
r, _ := http.NewRequest("POST", "/v1/account/token", nil)
err = s.ensureUser(next)(httptest.NewRecorder(), r, anon)
require.Equal(t, errHTTPUnauthorized, err)
require.False(t, nextCalled)
}
func TestServer_AuthorizeTopic_UserSurvivesConcurrentAnonymousRequest(t *testing.T) {
// The reverse direction: an authenticated request must not be authorized as nobody (and be
// rejected under deny-all) because an anonymous request from the same IP overlapped with it
// (#1051, #2113)
c := newTestConfigWithAuthFile(t, "")
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
require.Nil(t, s.userManager.AddUser("phil", "phil12345", user.RoleUser, false))
require.Nil(t, s.userManager.AllowAccess("phil", "mytopic", user.PermissionReadWrite))
u, err := s.userManager.User("phil")
require.Nil(t, err)
ip := netip.MustParseAddr("9.9.9.9")
authed := s.visitor(ip, u)
s.visitor(ip, nil) // The concurrent anonymous request, made deterministic
nextCalled := false
next := func(w http.ResponseWriter, r *http.Request, v *visitor) error {
nextCalled = true
return nil
}
r, _ := http.NewRequest("PUT", "/mytopic", nil)
require.Nil(t, s.authorizeTopicWrite(next)(httptest.NewRecorder(), r, authed))
require.True(t, nextCalled)
}