Просмотр исходного кода

fix(api): return 401 for invalid Bearer token instead of 404 (#6459)

When Authorization: Bearer is present but does not match (or is disabled),
respond with 401 Unauthorized so script authors can distinguish auth failure
from a wrong webBasePath. Requests with no Authorization header still get
404 masking; wrong base paths continue to 404 via NoRoute.

Fixes #6255

Co-authored-by: mrchatam <[email protected]>
mrchatam 2 часов назад
Родитель
Сommit
bdd351bd15
2 измененных файлов с 19 добавлено и 8 удалено
  1. 5 1
      internal/web/controller/api.go
  2. 14 7
      internal/web/controller/api_auth_test.go

+ 5 - 1
internal/web/controller/api.go

@@ -61,7 +61,11 @@ func (a *APIController) checkAPIAuth(c *gin.Context) {
 		}
 	}
 	if !session.IsLogin(c) {
-		if c.GetHeader("X-Requested-With") == "XMLHttpRequest" {
+		// A presented Bearer token is not an anonymous scan: return 401 so
+		// callers can distinguish a bad/disabled token from a wrong base path
+		// (NoRoute still 404s). XHR keeps 401; bare unauthenticated stays 404.
+		authHdr := c.GetHeader("Authorization")
+		if strings.HasPrefix(authHdr, "Bearer ") || c.GetHeader("X-Requested-With") == "XMLHttpRequest" {
 			c.AbortWithStatus(http.StatusUnauthorized)
 		} else {
 			c.AbortWithStatus(http.StatusNotFound)

+ 14 - 7
internal/web/controller/api_auth_test.go

@@ -219,18 +219,22 @@ func TestCheckAPIAuth_EmptyVerifiedChainsFallsThrough(t *testing.T) {
 	}
 }
 
-// TestCheckAPIAuth_RejectsUnauthenticated characterizes the reject paths: no
-// bearer token and no session yields 401 for XHR callers and 404 otherwise.
+// TestCheckAPIAuth_RejectsUnauthenticated characterizes the reject paths:
+// no credential → 404 (masking); XHR or a presented (but invalid) Bearer → 401
+// so script authors can tell auth failure from a wrong base path.
 func TestCheckAPIAuth_RejectsUnauthenticated(t *testing.T) {
 	engine, _ := newAPIAuthTestEngine(t)
 
 	cases := []struct {
-		name string
-		xhr  bool
-		want int
+		name   string
+		xhr    bool
+		bearer string // empty = omit Authorization header
+		want   int
 	}{
-		{"xhr gets 401", true, http.StatusUnauthorized},
-		{"non-xhr gets 404", false, http.StatusNotFound},
+		{"xhr gets 401", true, "", http.StatusUnauthorized},
+		{"non-xhr gets 404", false, "", http.StatusNotFound},
+		{"invalid bearer gets 401", false, "definitely-not-a-token", http.StatusUnauthorized},
+		{"invalid bearer xhr gets 401", true, "definitely-not-a-token", http.StatusUnauthorized},
 	}
 	for _, c := range cases {
 		t.Run(c.name, func(t *testing.T) {
@@ -238,6 +242,9 @@ func TestCheckAPIAuth_RejectsUnauthenticated(t *testing.T) {
 			if c.xhr {
 				req.Header.Set("X-Requested-With", "XMLHttpRequest")
 			}
+			if c.bearer != "" {
+				req.Header.Set("Authorization", "Bearer "+c.bearer)
+			}
 			w := httptest.NewRecorder()
 			engine.ServeHTTP(w, req)
 			if w.Code != c.want {