mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-05-11 16:59:59 +08:00
Compare commits
4 Commits
v1.10.0
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
910421c792 | ||
|
|
4d18efc0aa | ||
|
|
5b974b82f2 | ||
|
|
a74928a478 |
4
go.mod
4
go.mod
@@ -14,7 +14,7 @@ require (
|
||||
github.com/grafana/pyroscope-go v1.2.7
|
||||
github.com/jackc/pgx/v5 v5.7.4
|
||||
github.com/jhump/protoreflect v1.17.0
|
||||
github.com/modelcontextprotocol/go-sdk v1.3.0
|
||||
github.com/modelcontextprotocol/go-sdk v1.2.0
|
||||
github.com/pelletier/go-toml/v2 v2.2.4
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/redis/go-redis/v9 v9.17.3
|
||||
@@ -71,7 +71,7 @@ require (
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/jsonschema-go v0.4.2 // indirect
|
||||
github.com/google/jsonschema-go v0.3.0 // indirect
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -74,8 +74,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
|
||||
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
|
||||
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -125,8 +125,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/modelcontextprotocol/go-sdk v1.3.0 h1:gMfZkv3DzQF5q/DcQePo5rahEY+sguyPfXDfNBcT0Zs=
|
||||
github.com/modelcontextprotocol/go-sdk v1.3.0/go.mod h1:AnQ//Qc6+4nIyyrB4cxBU7UW9VibK4iOZBeyP/rF1IE=
|
||||
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
|
||||
github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/zeromicro/go-zero/core/logc"
|
||||
@@ -99,10 +100,17 @@ func WithUnauthorizedCallback(callback UnauthorizedCallback) AuthorizeOption {
|
||||
|
||||
func detailAuthLog(r *http.Request, reason string) {
|
||||
// discard dump error, only for debug purpose
|
||||
details, _ := httputil.DumpRequest(r, true)
|
||||
// Skip dumping request body for multipart/form-data to avoid reading large files
|
||||
dumpBody := !isMultipartFormData(r)
|
||||
details, _ := httputil.DumpRequest(r, dumpBody)
|
||||
logc.Errorf(r.Context(), "authorize failed: %s\n=> %+v", reason, string(details))
|
||||
}
|
||||
|
||||
func isMultipartFormData(r *http.Request) bool {
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
return strings.Contains(contentType, "multipart/form-data")
|
||||
}
|
||||
|
||||
func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback UnauthorizedCallback) {
|
||||
writer := response.NewHeaderOnceResponseWriter(w)
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -90,6 +91,128 @@ func TestAuthHandler_NilError(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthHandlerWithJSONBody(t *testing.T) {
|
||||
const key = "B63F477D-BBA3-4E52-96D3-C0034C27694A"
|
||||
|
||||
// Create a request with JSON body
|
||||
jsonBody := `{"username":"test","password":"secret"}`
|
||||
req := httptest.NewRequest(http.MethodPost, "http://localhost/login",
|
||||
strings.NewReader(jsonBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
// Missing authorization header to trigger the unauthorized path
|
||||
|
||||
handler := Authorize(key)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
handler.ServeHTTP(resp, req)
|
||||
|
||||
// Should return unauthorized
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.Code)
|
||||
}
|
||||
|
||||
func TestAuthHandlerWithMultipartFormData(t *testing.T) {
|
||||
const key = "B63F477D-BBA3-4E52-96D3-C0034C27694A"
|
||||
|
||||
// Create a multipart form-data request
|
||||
// We don't need actual body content since we're testing that
|
||||
// the body is NOT read when Content-Type is multipart/form-data
|
||||
req := httptest.NewRequest(http.MethodPost, "http://localhost/upload",
|
||||
http.NoBody)
|
||||
req.Header.Set("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary")
|
||||
// Missing authorization header to trigger the unauthorized path
|
||||
|
||||
handler := Authorize(key)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
handler.ServeHTTP(resp, req)
|
||||
|
||||
// Should return unauthorized
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.Code)
|
||||
}
|
||||
|
||||
func TestAuthHandlerWithMultipartFormDataLargeFile(t *testing.T) {
|
||||
const key = "B63F477D-BBA3-4E52-96D3-C0034C27694A"
|
||||
|
||||
// Create a multipart form-data request with a simulated large file
|
||||
// This tests that the body is NOT consumed when Content-Type is multipart/form-data
|
||||
largeContent := make([]byte, 1024*1024) // 1MB of data
|
||||
for i := range largeContent {
|
||||
largeContent[i] = byte(i % 256)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "http://localhost/upload",
|
||||
http.NoBody)
|
||||
req.Header.Set("Content-Type", "multipart/form-data; boundary=----WebKitFormBoundary")
|
||||
req.Header.Set("Content-Length", "1048576")
|
||||
// Missing authorization header to trigger the unauthorized path
|
||||
|
||||
handler := Authorize(key)(
|
||||
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
// This should complete quickly without reading the body
|
||||
start := time.Now()
|
||||
handler.ServeHTTP(resp, req)
|
||||
elapsed := time.Since(start)
|
||||
|
||||
// Should return unauthorized
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.Code)
|
||||
// Should complete in less than 100ms (without reading 1MB of data)
|
||||
assert.Less(t, elapsed, 100*time.Millisecond)
|
||||
}
|
||||
|
||||
func TestIsMultipartFormData(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
contentType string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "multipart/form-data",
|
||||
contentType: "multipart/form-data",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "multipart/form-data with boundary",
|
||||
contentType: "multipart/form-data; boundary=----WebKitFormBoundary",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "application/json",
|
||||
contentType: "application/json",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "application/x-www-form-urlencoded",
|
||||
contentType: "application/x-www-form-urlencoded",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty content type",
|
||||
contentType: "",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPost, "http://localhost", http.NoBody)
|
||||
req.Header.Set("Content-Type", tt.contentType)
|
||||
result := isMultipartFormData(req)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func buildToken(secretKey string, payloads map[string]any, seconds int64) (string, error) {
|
||||
now := time.Now().Unix()
|
||||
claims := make(jwt.MapClaims)
|
||||
|
||||
Reference in New Issue
Block a user