From 65d02166cd749832e76c344663381af5e358e783 Mon Sep 17 00:00:00 2001 From: Germano Eichenberg Date: Tue, 16 Sep 2025 14:44:45 -0300 Subject: [PATCH 1/3] Handle basic auth without token validation --- lib/discord.go | 6 ++++- lib/queue.go | 9 ++++--- lib/queue_manager.go | 8 +++--- lib/queue_manager_test.go | 57 +++++++++++++++++++++++++++++++++++++++ lib/util.go | 23 +++++++++++++++- lib/util_test.go | 25 +++++++++++++++-- 6 files changed, 117 insertions(+), 11 deletions(-) create mode 100644 lib/queue_manager_test.go diff --git a/lib/discord.go b/lib/discord.go index cf99f86..3251deb 100644 --- a/lib/discord.go +++ b/lib/discord.go @@ -137,7 +137,11 @@ func GetBotGlobalLimit(token string, user *BotUserResponse) (uint, error) { } } - if strings.HasPrefix(token, "Bearer") { + if HasAuthPrefix(token, "Bearer") { + return 50, nil + } + + if HasAuthPrefix(token, "Basic") { return 50, nil } diff --git a/lib/queue.go b/lib/queue.go index 203383a..ea15102 100644 --- a/lib/queue.go +++ b/lib/queue.go @@ -46,13 +46,14 @@ func NewRequestQueue(processor func(ctx context.Context, item *QueueItem) (*http queueType := NoAuth var user *BotUserResponse var err error - if !strings.HasPrefix(token, "Bearer") { + switch { + case HasAuthPrefix(token, "Bearer"): + queueType = Bearer + case token != "" && !HasAuthPrefix(token, "Basic"): user, err = GetBotUser(token) - if err != nil && token != "" { + if err != nil { return nil, err } - } else { - queueType = Bearer } limit, err := GetBotGlobalLimit(token, user) diff --git a/lib/queue_manager.go b/lib/queue_manager.go index f0f1ce4..e5346dd 100644 --- a/lib/queue_manager.go +++ b/lib/queue_manager.go @@ -261,12 +261,14 @@ func (m *QueueManager) DiscordRequestHandler(resp http.ResponseWriter, req *http func (m *QueueManager) GetRequestRoutingInfo(req *http.Request, token string) (routingHash uint64, path string, queueType QueueType) { path = GetOptimisticBucketPath(req.URL.Path, req.Method) queueType = NoAuth - if strings.HasPrefix(token, "Bearer") { + routingHash = HashCRC64(path) + + switch { + case HasAuthPrefix(token, "Bearer"): queueType = Bearer routingHash = HashCRC64(token) - } else { + case token != "" && !HasAuthPrefix(token, "Basic"): queueType = Bot - routingHash = HashCRC64(path) } return } diff --git a/lib/queue_manager_test.go b/lib/queue_manager_test.go new file mode 100644 index 0000000..516b4a2 --- /dev/null +++ b/lib/queue_manager_test.go @@ -0,0 +1,57 @@ +package lib + +import ( + "net/http" + "testing" +) + +func TestGetRequestRoutingInfoBasicAuth(t *testing.T) { + manager := &QueueManager{} + req, err := http.NewRequest("POST", "https://discord.com/api/v10/oauth2/token/revoke", nil) + if err != nil { + t.Fatalf("failed to create request: %v", err) + } + + hash, path, queueType := manager.GetRequestRoutingInfo(req, "Basic ZmFrZVRva2Vu") + if queueType != NoAuth { + t.Fatalf("expected queue type %v, got %v", NoAuth, queueType) + } + + if hash != HashCRC64(path) { + t.Fatalf("expected routing hash to match path hash") + } +} + +func TestGetRequestRoutingInfoBearerAuth(t *testing.T) { + manager := &QueueManager{} + req, err := http.NewRequest("GET", "https://discord.com/api/v10/users/@me", nil) + if err != nil { + t.Fatalf("failed to create request: %v", err) + } + + hash, _, queueType := manager.GetRequestRoutingInfo(req, "Bearer some-token") + if queueType != Bearer { + t.Fatalf("expected queue type %v, got %v", Bearer, queueType) + } + + if hash != HashCRC64("Bearer some-token") { + t.Fatalf("expected bearer routing hash to use token") + } +} + +func TestGetRequestRoutingInfoBotToken(t *testing.T) { + manager := &QueueManager{} + req, err := http.NewRequest("GET", "https://discord.com/api/v10/channels/123/messages", nil) + if err != nil { + t.Fatalf("failed to create request: %v", err) + } + + hash, path, queueType := manager.GetRequestRoutingInfo(req, "Bot Abc") + if queueType != Bot { + t.Fatalf("expected queue type %v, got %v", Bot, queueType) + } + + if hash != HashCRC64(path) { + t.Fatalf("expected bot routing hash to match path hash") + } +} diff --git a/lib/util.go b/lib/util.go index 73c2712..0305bc3 100644 --- a/lib/util.go +++ b/lib/util.go @@ -34,6 +34,7 @@ func GetBotId(token string) string { } else { token = strings.ReplaceAll(token, "Bot ", "") token = strings.ReplaceAll(token, "Bearer ", "") + token = strings.ReplaceAll(token, "Basic ", "") token = strings.Split(token, ".")[0] token, err := base64.StdEncoding.DecodeString(token) if err != nil { @@ -43,4 +44,24 @@ func GetBotId(token string) string { } } return clientId -} \ No newline at end of file +} + +// HasAuthPrefix checks if the provided authorization header value starts with the +// given scheme. Comparison is performed case-insensitively and requires the +// scheme to be followed by at least one space as mandated by RFC 7235. +func HasAuthPrefix(token, scheme string) bool { + if len(token) <= len(scheme) { + return false + } + + if !strings.EqualFold(token[:len(scheme)], scheme) { + return false + } + + switch token[len(scheme)] { + case ' ', '\t': + return true + default: + return false + } +} diff --git a/lib/util_test.go b/lib/util_test.go index fa36a12..0f6b164 100644 --- a/lib/util_test.go +++ b/lib/util_test.go @@ -6,6 +6,7 @@ import ( ) const knownData = "test data" + // Calculated using ISO table const knownHash = 10232006911339297906 @@ -13,7 +14,7 @@ func TestHashWorks(t *testing.T) { HashCRC64(knownData) } -//Test for correctness +// Test for correctness func TestHashIsConsistent(t *testing.T) { ret := HashCRC64(knownData) if ret != knownHash { @@ -21,7 +22,7 @@ func TestHashIsConsistent(t *testing.T) { } } -//Test for consistency when function is used for other data +// Test for consistency when function is used for other data func TestHashIsConsistentAcrossMultipleRuns(t *testing.T) { for i := 0; i < 50000; i++ { HashCRC64(strconv.Itoa(i)) @@ -33,3 +34,23 @@ func TestHashIsConsistentAcrossMultipleRuns(t *testing.T) { } } +func TestHasAuthPrefix(t *testing.T) { + tests := []struct { + name string + token string + scheme string + expected bool + }{ + {name: "basic with space", token: "Basic Zm9vOmJhcg==", scheme: "Basic", expected: true}, + {name: "basic lowercase", token: "basic Zm9vOmJhcg==", scheme: "Basic", expected: true}, + {name: "missing space", token: "BasicZm9v", scheme: "Basic", expected: false}, + {name: "different scheme", token: "Bot foo", scheme: "Basic", expected: false}, + {name: "bearer", token: "Bearer token", scheme: "Bearer", expected: true}, + } + + for _, tc := range tests { + if result := HasAuthPrefix(tc.token, tc.scheme); result != tc.expected { + t.Errorf("%s: expected %v, got %v", tc.name, tc.expected, result) + } + } +} From 7bb6522546e6d62f622da8286297f588f335ea14 Mon Sep 17 00:00:00 2001 From: Germano Eichenberg Date: Tue, 16 Sep 2025 15:05:26 -0300 Subject: [PATCH 2/3] Update lib/util_test.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- lib/util_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/util_test.go b/lib/util_test.go index 0f6b164..4915880 100644 --- a/lib/util_test.go +++ b/lib/util_test.go @@ -43,6 +43,8 @@ func TestHasAuthPrefix(t *testing.T) { }{ {name: "basic with space", token: "Basic Zm9vOmJhcg==", scheme: "Basic", expected: true}, {name: "basic lowercase", token: "basic Zm9vOmJhcg==", scheme: "Basic", expected: true}, + {name: "basic with tab", token: "Basic\tZm9vOmJhcg==", scheme: "Basic", expected: true}, + {name: "basic only scheme", token: "Basic ", scheme: "Basic", expected: true}, {name: "missing space", token: "BasicZm9v", scheme: "Basic", expected: false}, {name: "different scheme", token: "Bot foo", scheme: "Basic", expected: false}, {name: "bearer", token: "Bearer token", scheme: "Bearer", expected: true}, From 9a67633f7744e44fee440ac06a20d9ed2bbf7f6f Mon Sep 17 00:00:00 2001 From: Germano Eichenberg Date: Tue, 16 Sep 2025 15:21:37 -0300 Subject: [PATCH 3/3] Update lib/util_test.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- lib/util_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/util_test.go b/lib/util_test.go index 4915880..93ac66f 100644 --- a/lib/util_test.go +++ b/lib/util_test.go @@ -11,7 +11,9 @@ const knownData = "test data" const knownHash = 10232006911339297906 func TestHashWorks(t *testing.T) { - HashCRC64(knownData) + if HashCRC64(knownData) != knownHash { + t.Fatalf("Invalid hash returned") + } } // Test for correctness