diff --git a/pkg/api/authentication_test.go b/pkg/api/authentication_test.go index 3bcf0720..af32aa41 100644 --- a/pkg/api/authentication_test.go +++ b/pkg/api/authentication_test.go @@ -19,6 +19,61 @@ func Test_AuthnNoToken(t *testing.T) { }) } +func Test_AuthnNoTokenNonV3(t *testing.T) { + ctx := context.Background() + testGRPCAndHTTP(t, ctx, func(t *testing.T, client messageclient.Client, server *Server) { + _, err := client.Publish(ctx, &messageV1.PublishRequest{ + Envelopes: []*messageV1.Envelope{ + { + ContentTopic: "/xmtp/0/m-0x1234/proto", + TimestampNs: 0, + Message: []byte{}, + }, + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "authorization token is not provided") + }) +} + +func Test_AuthnNoTokenV3(t *testing.T) { + ctx := context.Background() + testGRPCAndHTTP(t, ctx, func(t *testing.T, client messageclient.Client, server *Server) { + _, err := client.Publish(ctx, &messageV1.PublishRequest{ + Envelopes: []*messageV1.Envelope{ + { + ContentTopic: "/xmtp/3/m-0x1234/proto", + TimestampNs: 0, + Message: []byte{}, + }, + }, + }) + require.NoError(t, err) + }) +} + +func Test_AuthnNoTokenMixedV0V3(t *testing.T) { + ctx := context.Background() + testGRPCAndHTTP(t, ctx, func(t *testing.T, client messageclient.Client, server *Server) { + _, err := client.Publish(ctx, &messageV1.PublishRequest{ + Envelopes: []*messageV1.Envelope{ + { + ContentTopic: "/xmtp/0/m-0x1234/proto", + TimestampNs: 0, + Message: []byte{}, + }, + { + ContentTopic: "/xmtp/3/m-0x1234/proto", + TimestampNs: 0, + Message: []byte{}, + }, + }, + }) + require.Error(t, err) + require.Contains(t, err.Error(), "authorization token is not provided") + }) +} + // Private key topic queries must be let through without authn func Test_AuthnAllowedWithoutAuthn(t *testing.T) { ctx := context.Background() diff --git a/pkg/api/config.go b/pkg/api/config.go index 02555534..d20ac9d0 100644 --- a/pkg/api/config.go +++ b/pkg/api/config.go @@ -35,6 +35,7 @@ type Config struct { // Options bundle command line options associated with the authn package. type AuthnOptions struct { Enable bool `long:"enable" description:"require client authentication via wallet tokens"` + EnableV3 bool `long:"enable-v3" description:"require client authentication for V3"` Ratelimits bool `long:"ratelimits" description:"apply rate limits per wallet"` AllowLists bool `long:"allowlists" description:"apply higher limits for allow listed wallets (requires authz and ratelimits)"` PrivilegedAddresses []string `long:"privileged-address" description:"allow this address to publish into other user's topics"` diff --git a/pkg/api/interceptor.go b/pkg/api/interceptor.go index f0581ec6..5581ba4b 100644 --- a/pkg/api/interceptor.go +++ b/pkg/api/interceptor.go @@ -67,9 +67,23 @@ func (wa *WalletAuthorizer) Stream() grpc.StreamServerInterceptor { } } +func (wa *WalletAuthorizer) isProtocolVersion3(request *messagev1.PublishRequest) bool { + envelopes := request.Envelopes + if envelopes == nil || len(envelopes) == 0 { + return false + } + // If any of the envelopes are not for a v3 topic, then we treat the request as non-v3 + for _, envelope := range envelopes { + if !strings.HasPrefix(envelope.ContentTopic, "/xmtp/3/") { + return false + } + } + return true +} + func (wa *WalletAuthorizer) requiresAuthorization(req interface{}) bool { - _, isPublish := req.(*messagev1.PublishRequest) - return isPublish + publishRequest, isPublish := req.(*messagev1.PublishRequest) + return isPublish && (!wa.isProtocolVersion3(publishRequest) || wa.AuthnConfig.EnableV3) } func (wa *WalletAuthorizer) authorize(ctx context.Context, req interface{}) error {