Skip to content

Commit 66dd310

Browse files
committed
is: Rate limit device creation by application ID instead of device ID
Previously, CreateEndDeviceRequest was rate-limited per device ID, which was ineffective since each new device has a unique ID. This allowed unlimited device creation attempts per application. Now rate limiting is applied per application ID, properly restricting the rate of device creation requests at the application level.
1 parent 3d0d2f1 commit 66dd310

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

pkg/ttnpb/end_device.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,6 +2990,18 @@ func (m *EndDevice) IDString() string {
29902990
return m.GetIds().IDString()
29912991
}
29922992

2993+
// RateLimitKey is the Implementation of the RateLimitKeyer interface
2994+
func (m *CreateEndDeviceRequest) RateLimitKey() string {
2995+
if dev := m.GetEndDevice(); dev != nil {
2996+
if ids := dev.GetIds(); ids != nil {
2997+
if appIDs := ids.GetApplicationIds(); appIDs != nil {
2998+
return appIDs.IDString()
2999+
}
3000+
}
3001+
}
3002+
return ""
3003+
}
3004+
29933005
// All ExtractRequestFields methods are used by github.com/grpc-ecosystem/go-grpc-middleware/tags.
29943006

29953007
func (m *ResetAndGetEndDeviceRequest) ExtractRequestFields(dst map[string]interface{}) {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright © 2025 The Things Network Foundation, The Things Industries B.V.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ttnpb_test
16+
17+
import (
18+
"testing"
19+
20+
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
21+
)
22+
23+
func TestCreateEndDeviceRequest_RateLimitKey(t *testing.T) {
24+
tests := []struct {
25+
name string
26+
req *ttnpb.CreateEndDeviceRequest
27+
expected string
28+
}{
29+
{
30+
name: "valid request",
31+
req: &ttnpb.CreateEndDeviceRequest{
32+
EndDevice: &ttnpb.EndDevice{
33+
Ids: &ttnpb.EndDeviceIdentifiers{
34+
ApplicationIds: &ttnpb.ApplicationIdentifiers{
35+
ApplicationId: "test-app",
36+
},
37+
DeviceId: "test-device-1",
38+
},
39+
},
40+
},
41+
expected: "test-app",
42+
},
43+
{
44+
name: "different device same app",
45+
req: &ttnpb.CreateEndDeviceRequest{
46+
EndDevice: &ttnpb.EndDevice{
47+
Ids: &ttnpb.EndDeviceIdentifiers{
48+
ApplicationIds: &ttnpb.ApplicationIdentifiers{
49+
ApplicationId: "test-app",
50+
},
51+
DeviceId: "test-device-2",
52+
},
53+
},
54+
},
55+
expected: "test-app",
56+
},
57+
{
58+
name: "nil request",
59+
req: &ttnpb.CreateEndDeviceRequest{},
60+
expected: "",
61+
},
62+
}
63+
64+
for _, tt := range tests {
65+
t.Run(tt.name, func(t *testing.T) {
66+
got := tt.req.RateLimitKey()
67+
if got != tt.expected {
68+
t.Errorf("RateLimitKey() = %v, want %v", got, tt.expected)
69+
}
70+
})
71+
}
72+
}

0 commit comments

Comments
 (0)