Skip to content

Commit 5c12538

Browse files
authored
feat: add proxying existing protected resource metadata (#73)
Signed-off-by: Jakob Steiner <[email protected]>
1 parent 45865b5 commit 5c12538

File tree

10 files changed

+269
-124
lines changed

10 files changed

+269
-124
lines changed

cmd/serve.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/hyprmcp/mcp-gateway/log"
1818
"github.com/hyprmcp/mcp-gateway/oauth"
1919
"github.com/hyprmcp/mcp-gateway/proxy"
20+
"github.com/hyprmcp/mcp-gateway/proxy/proxyutil"
2021
"github.com/spf13/cobra"
2122
)
2223

@@ -52,7 +53,7 @@ func runServe(ctx context.Context, opts ServeOptions) error {
5253
authUrl, err := url.Parse(cfg.Authorization.Server)
5354
if err != nil {
5455
done <- fmt.Errorf("auth proxy serve failed: %w", err)
55-
} else if err := http.ListenAndServe(opts.AuthProxyAddr, &httputil.ReverseProxy{Rewrite: proxy.RewriteHostFunc(authUrl)}); !errors.Is(err, http.ErrServerClosed) {
56+
} else if err := http.ListenAndServe(opts.AuthProxyAddr, &httputil.ReverseProxy{Rewrite: proxyutil.RewriteHostFunc(authUrl)}); !errors.Is(err, http.ErrServerClosed) {
5657
done <- fmt.Errorf("auth proxy serve failed: %w", err)
5758
} else {
5859
done <- nil
@@ -120,7 +121,7 @@ func newRouter(ctx context.Context, config *config.Config) (http.Handler, error)
120121

121122
for _, proxyConfig := range config.Proxy {
122123
if proxyConfig.Http != nil && proxyConfig.Http.Url != nil {
123-
handler := proxy.NewProxyHandler(&proxyConfig)
124+
handler := proxy.NewProxyHandler(&proxyConfig, oauthManager.UpdateWWWAuthenticateHeader)
124125
handler = htmlHandler.Handler(handler)
125126

126127
if proxyConfig.Authentication.Enabled {

go.sum

Lines changed: 18 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
44
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
55
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
66
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
7-
github.com/dexidp/dex/api/v2 v2.3.0 h1:gv79YqTBTGU6z/QE3RNFzlS6KrPRUudVUN8o858gpCc=
8-
github.com/dexidp/dex/api/v2 v2.3.0/go.mod h1:y9As69T4WZOERCS/CfB9D8Dbb12tafU9ywv2ZZLf4EI=
97
github.com/dexidp/dex/api/v2 v2.4.0 h1:gNba7n6BKVp8X4Jp24cxYn5rIIGhM6kDOXcZoL6tr9A=
108
github.com/dexidp/dex/api/v2 v2.4.0/go.mod h1:/p550ADvFFh7K95VmhUD+jgm15VdaNnab9td8DHOpyI=
119
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
@@ -25,16 +23,6 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
2523
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
2624
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2725
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
28-
github.com/google/jsonschema-go v0.2.0 h1:Uh19091iHC56//WOsAd1oRg6yy1P9BpSvpjOL6RcjLQ=
29-
github.com/google/jsonschema-go v0.2.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
30-
github.com/google/jsonschema-go v0.2.1-0.20250825175020-748c325cec76 h1:mBlBwtDebdDYr+zdop8N62a44g+Nbv7o2KjWyS1deR4=
31-
github.com/google/jsonschema-go v0.2.1-0.20250825175020-748c325cec76/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
32-
github.com/google/jsonschema-go v0.2.1-0.20250828145618-7d3a7746ff83 h1:LYZft4tK/R6x6vqNemVJHsDkOtBZFhJh8mFWGyaDAfE=
33-
github.com/google/jsonschema-go v0.2.1-0.20250828145618-7d3a7746ff83/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
34-
github.com/google/jsonschema-go v0.2.1 h1:Z3iINWAUmvS4+m9cMP5lWbn6WlX8Hy4rpUS4pULVliQ=
35-
github.com/google/jsonschema-go v0.2.1/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
36-
github.com/google/jsonschema-go v0.2.3 h1:dkP3B96OtZKKFvdrUSaDkL+YDx8Uw9uC4Y+eukpCnmM=
37-
github.com/google/jsonschema-go v0.2.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
3826
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
3927
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
4028
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -55,20 +43,12 @@ github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZ
5543
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
5644
github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI=
5745
github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk=
58-
github.com/lestrrat-go/jwx/v3 v3.0.10 h1:XuoCBhZBncRIjMQ32HdEc76rH0xK/Qv2wq5TBouYJDw=
59-
github.com/lestrrat-go/jwx/v3 v3.0.10/go.mod h1:kNMedLgTpHvPJkK5EMVa1JFz+UVyY2dMmZKu3qjl/Pk=
6046
github.com/lestrrat-go/jwx/v3 v3.0.11 h1:yEeUGNUuNjcez/Voxvr7XPTYNraSQTENJgtVTfwvG/w=
6147
github.com/lestrrat-go/jwx/v3 v3.0.11/go.mod h1:XSOAh2SiXm0QgRe3DulLZLyt+wUuEdFo81zuKTLcvgQ=
6248
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
6349
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
6450
github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
6551
github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
66-
github.com/modelcontextprotocol/go-sdk v0.3.0 h1:/1XC6+PpdKfE4CuFJz8/goo0An31bu8n8G8d3BkeJoY=
67-
github.com/modelcontextprotocol/go-sdk v0.3.0/go.mod h1:71VUZVa8LL6WARvSgLJ7DMpDWSeomT4uBv8g97mGBvo=
68-
github.com/modelcontextprotocol/go-sdk v0.3.1 h1:0z04yIPlSwTluuelCBaL+wUag4YeflIU2Fr4Icb7M+o=
69-
github.com/modelcontextprotocol/go-sdk v0.3.1/go.mod h1:whv0wHnsTphwq7CTiKYHkLtwLC06WMoY2KpO+RB9yXQ=
70-
github.com/modelcontextprotocol/go-sdk v0.4.0 h1:RJ6kFlneHqzTKPzlQqiunrz9nbudSZcYLmLHLsokfoU=
71-
github.com/modelcontextprotocol/go-sdk v0.4.0/go.mod h1:whv0wHnsTphwq7CTiKYHkLtwLC06WMoY2KpO+RB9yXQ=
7252
github.com/modelcontextprotocol/go-sdk v0.5.0 h1:WXRHx/4l5LF5MZboeIJYn7PMFCrMNduGGVapYWFgrF8=
7353
github.com/modelcontextprotocol/go-sdk v0.5.0/go.mod h1:degUj7OVKR6JcYbDF+O99Fag2lTSTbamZacbGTRTSGU=
7454
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@@ -80,19 +60,15 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
8060
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
8161
github.com/sourcegraph/jsonrpc2 v0.2.1 h1:2GtljixMQYUYCmIg7W9aF2dFmniq/mOr2T9tFRh6zSQ=
8262
github.com/sourcegraph/jsonrpc2 v0.2.1/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo=
83-
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
84-
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
8563
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
8664
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
87-
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
88-
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
8965
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
9066
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
9167
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9268
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
9369
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
94-
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
95-
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
70+
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
71+
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
9672
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
9773
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
9874
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
@@ -103,48 +79,36 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
10379
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
10480
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
10581
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
106-
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
107-
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
108-
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
109-
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
110-
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
111-
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
112-
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
113-
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
114-
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
115-
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
82+
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
83+
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
84+
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
85+
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
86+
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
87+
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
88+
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
89+
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
90+
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
91+
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
11692
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
11793
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
118-
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
119-
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
12094
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
12195
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
122-
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
123-
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
12496
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
12597
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
126-
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
127-
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
128-
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
129-
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
98+
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
99+
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
130100
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
131101
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
132-
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
133-
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
134102
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
135103
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
136-
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
137-
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
104+
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
105+
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
106+
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
107+
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
138108
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
139109
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
140-
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
141-
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
142-
google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=
143-
google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
144110
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
145111
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
146-
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
147-
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
148112
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
149113
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
150114
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

oauth/authorization_server_metadata.go

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,56 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7+
"net/http/httputil"
78
"net/url"
89

910
"github.com/hyprmcp/mcp-gateway/config"
1011
"github.com/hyprmcp/mcp-gateway/log"
12+
"github.com/hyprmcp/mcp-gateway/proxy/proxyutil"
1113
"go.uber.org/multierr"
1214
)
1315

1416
const AuthorizationServerMetadataPath = "/.well-known/oauth-authorization-server"
1517
const OIDCMetadataPath = "/.well-known/openid-configuration"
1618

1719
func NewAuthorizationServerMetadataHandler(config *config.Config) http.Handler {
18-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
19-
metadata, err := GetMedatata(config.Authorization.Server)
20-
if err != nil {
21-
log.Get(r.Context()).Error(err, "failed to get authorization server metadata from upstream")
22-
http.Error(w, "Failed to retrieve authorization server metadata", http.StatusInternalServerError)
20+
if len(config.Proxy) == 1 && !config.Proxy[0].Authentication.Enabled {
21+
return &httputil.ReverseProxy{
22+
Rewrite: proxyutil.RewriteHostFunc((*url.URL)(config.Proxy[0].Http.Url)),
23+
ModifyResponse: proxyutil.RemoveCORSHeaders,
2324
}
25+
} else {
26+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27+
metadata, err := GetMedatata(config.Authorization.Server)
28+
if err != nil {
29+
log.Get(r.Context()).Error(err, "failed to get authorization server metadata from upstream")
30+
http.Error(w, "Failed to retrieve authorization server metadata", http.StatusInternalServerError)
31+
}
2432

25-
if config.Authorization.GetDynamicClientRegistration().Enabled {
26-
if _, ok := metadata["registration_endpoint"]; !ok {
27-
registrationURI, _ := url.Parse(config.Host.String())
28-
registrationURI.Path = DynamicClientRegistrationPath
29-
metadata["registration_endpoint"] = registrationURI.String()
30-
log.Get(r.Context()).Info("Adding registration endpoint to authorization server metadata",
31-
"url", metadata["registration_endpoint"])
33+
if config.Authorization.GetDynamicClientRegistration().Enabled {
34+
if _, ok := metadata["registration_endpoint"]; !ok {
35+
registrationURI, _ := url.Parse(config.Host.String())
36+
registrationURI.Path = DynamicClientRegistrationPath
37+
metadata["registration_endpoint"] = registrationURI.String()
38+
log.Get(r.Context()).Info("Adding registration endpoint to authorization server metadata",
39+
"url", metadata["registration_endpoint"])
40+
}
3241
}
33-
}
3442

35-
if config.Authorization.AuthorizationProxyEnabled {
36-
authorizationURI, _ := url.Parse(config.Host.String())
37-
authorizationURI.Path = AuthorizationPath
38-
metadata["authorization_endpoint"] = authorizationURI.String()
39-
log.Get(r.Context()).Info("Adding authorization endpoint to authorization server metadata",
40-
"url", metadata["authorization_endpoint"])
41-
}
43+
if config.Authorization.AuthorizationProxyEnabled {
44+
authorizationURI, _ := url.Parse(config.Host.String())
45+
authorizationURI.Path = AuthorizationPath
46+
metadata["authorization_endpoint"] = authorizationURI.String()
47+
log.Get(r.Context()).Info("Adding authorization endpoint to authorization server metadata",
48+
"url", metadata["authorization_endpoint"])
49+
}
4250

43-
w.Header().Set("Content-Type", "application/json")
44-
if err := json.NewEncoder(w).Encode(metadata); err != nil {
45-
log.Get(r.Context()).Error(err, "failed to encode authorization server metadata response")
46-
}
47-
})
51+
w.Header().Set("Content-Type", "application/json")
52+
if err := json.NewEncoder(w).Encode(metadata); err != nil {
53+
log.Get(r.Context()).Error(err, "failed to encode authorization server metadata response")
54+
}
55+
})
56+
}
4857
}
4958

5059
func GetMedatata(server string) (map[string]any, error) {

oauth/context.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package oauth
22

33
import (
44
"context"
5+
"net/url"
56

67
"github.com/lestrrat-go/jwx/v3/jwt"
78
)
89

910
type tokenKey struct{}
1011
type rawTokenKey struct{}
12+
type originalURLKey struct{}
1113

1214
func TokenContext(parent context.Context, token jwt.Token, rawToken string) context.Context {
1315
parent = context.WithValue(parent, tokenKey{}, token)
@@ -30,3 +32,15 @@ func GetRawToken(ctx context.Context) string {
3032
return ""
3133
}
3234
}
35+
36+
func WithOriginalURL(ctx context.Context, url *url.URL) context.Context {
37+
return context.WithValue(ctx, originalURLKey{}, url)
38+
}
39+
40+
func GetOriginalURL(ctx context.Context) *url.URL {
41+
if url, ok := ctx.Value(originalURLKey{}).(*url.URL); ok {
42+
return url
43+
}
44+
45+
return nil
46+
}

oauth/misc.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package oauth
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/http/httputil"
7+
"net/url"
8+
"strings"
9+
)
10+
11+
func RewriteSetOriginalURL(r *httputil.ProxyRequest) {
12+
r.Out = r.Out.WithContext(WithOriginalURL(r.Out.Context(), r.In.URL))
13+
}
14+
15+
func (mgr *Manager) UpdateWWWAuthenticateHeader(resp *http.Response) error {
16+
if resp.StatusCode == http.StatusUnauthorized {
17+
realRequestURL := GetOriginalURL(resp.Request.Context())
18+
upstreamMetaURL, _ := url.Parse(resp.Request.URL.String())
19+
upstreamMetaURL.Path, _ = url.JoinPath(ProtectedResourcePath, upstreamMetaURL.Path)
20+
upstreamMetaURLStr := upstreamMetaURL.String()
21+
22+
if value := resp.Header.Get("WWW-Authenticate"); value != "" {
23+
valueParts := strings.Split(value, " ")
24+
for i, part := range valueParts {
25+
if after, ok := strings.CutPrefix(part, "resource_metadata="); ok {
26+
if resourceMetadataOrig := strings.Trim(after, `"`); resourceMetadataOrig != "" {
27+
upstreamMetaURLStr = resourceMetadataOrig
28+
valueParts[i] = fmt.Sprintf(`resource_metadata="%s"`, mgr.getMetadataURL(realRequestURL))
29+
resp.Header.Set("WWW-Authenticate", strings.Join(valueParts, " "))
30+
break
31+
}
32+
}
33+
}
34+
}
35+
36+
// We also store the metadata URL if it was not found in the WWW-Authenticate header.
37+
// This is necessary to ensure that we don't return the wrong metadata later when the
38+
// client calls our metadata endpoint because of some heuristic.
39+
upstreamMetadataURLs.Store(strings.Trim(realRequestURL.Path, `/`), upstreamMetaURLStr)
40+
}
41+
42+
return nil
43+
}

oauth/oauth.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,14 @@ func (mgr *Manager) Handler(next http.Handler) http.Handler {
9999

100100
func (mgr *Manager) unauthorizedHandler() http.HandlerFunc {
101101
return func(w http.ResponseWriter, r *http.Request) {
102-
metadataURL, _ := url.Parse(mgr.config.Host.String())
103-
metadataURL.Path = ProtectedResourcePath
104-
metadataURL = metadataURL.JoinPath(r.URL.Path)
105-
w.Header().Set(
106-
"WWW-Authenticate",
107-
fmt.Sprintf(`Bearer resource_metadata="%s"`, metadataURL.String()),
108-
)
102+
w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer resource_metadata="%s"`, mgr.getMetadataURL(r.URL)))
109103
w.WriteHeader(http.StatusUnauthorized)
110104
}
111105
}
106+
107+
func (mgr *Manager) getMetadataURL(u *url.URL) *url.URL {
108+
metadataURL, _ := url.Parse(mgr.config.Host.String())
109+
metadataURL.Path = ProtectedResourcePath
110+
metadataURL = metadataURL.JoinPath(u.Path)
111+
return metadataURL
112+
}

0 commit comments

Comments
 (0)