mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
071383bbe8
# Enhanced OAuth2 and MCP Compliance for API Authentication This PR improves OAuth2 and MCP (Microsoft Cloud for Sovereignty) compliance by: 1. Adding RFC 9728 compliant `WWW-Authenticate` headers with resource metadata URLs 2. Passing the configured `AccessURL` to API key middleware for proper audience validation 3. Creating specialized CORS handling for OAuth2 and MCP endpoints with appropriate headers 4. Making the `state` parameter optional in OAuth2 authorization requests These changes ensure proper OAuth2 token audience validation against the configured access URL and improve interoperability with OAuth2 clients by providing better error responses and metadata discovery. Signed-off-by: Thomas Kosiewski <tk@coder.com>
123 lines
3.3 KiB
Go
123 lines
3.3 KiB
Go
package httpmw
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/go-chi/cors"
|
|
|
|
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
|
|
)
|
|
|
|
const (
|
|
// Server headers.
|
|
AccessControlAllowOriginHeader = "Access-Control-Allow-Origin"
|
|
AccessControlAllowCredentialsHeader = "Access-Control-Allow-Credentials"
|
|
AccessControlAllowMethodsHeader = "Access-Control-Allow-Methods"
|
|
AccessControlAllowHeadersHeader = "Access-Control-Allow-Headers"
|
|
VaryHeader = "Vary"
|
|
|
|
// Client headers.
|
|
OriginHeader = "Origin"
|
|
AccessControlRequestMethodsHeader = "Access-Control-Request-Methods"
|
|
AccessControlRequestHeadersHeader = "Access-Control-Request-Headers"
|
|
)
|
|
|
|
//nolint:revive
|
|
func Cors(allowAll bool, origins ...string) func(next http.Handler) http.Handler {
|
|
if len(origins) == 0 {
|
|
// The default behavior is '*', so putting the empty string defaults to
|
|
// the secure behavior of blocking CORS requests.
|
|
origins = []string{""}
|
|
}
|
|
if allowAll {
|
|
origins = []string{"*"}
|
|
}
|
|
|
|
// Standard CORS for most endpoints
|
|
standardCors := cors.Handler(cors.Options{
|
|
AllowedOrigins: origins,
|
|
// We only need GET for latency requests
|
|
AllowedMethods: []string{http.MethodOptions, http.MethodGet},
|
|
AllowedHeaders: []string{"Accept", "Content-Type", "X-LATENCY-CHECK", "X-CSRF-TOKEN"},
|
|
// Do not send any cookies
|
|
AllowCredentials: false,
|
|
})
|
|
|
|
// Permissive CORS for OAuth2 and MCP endpoints
|
|
permissiveCors := cors.Handler(cors.Options{
|
|
AllowedOrigins: []string{"*"},
|
|
AllowedMethods: []string{
|
|
http.MethodGet,
|
|
http.MethodPost,
|
|
http.MethodDelete,
|
|
http.MethodOptions,
|
|
},
|
|
AllowedHeaders: []string{
|
|
"Content-Type",
|
|
"Accept",
|
|
"Authorization",
|
|
"x-api-key",
|
|
"Mcp-Session-Id",
|
|
"MCP-Protocol-Version",
|
|
"Last-Event-ID",
|
|
},
|
|
ExposedHeaders: []string{
|
|
"Content-Type",
|
|
"Authorization",
|
|
"x-api-key",
|
|
"Mcp-Session-Id",
|
|
"MCP-Protocol-Version",
|
|
},
|
|
MaxAge: 86400, // 24 hours in seconds
|
|
AllowCredentials: false,
|
|
})
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Use permissive CORS for OAuth2, MCP, and well-known endpoints
|
|
if strings.HasPrefix(r.URL.Path, "/oauth2/") ||
|
|
strings.HasPrefix(r.URL.Path, "/api/experimental/mcp/") ||
|
|
strings.HasPrefix(r.URL.Path, "/.well-known/oauth-") {
|
|
permissiveCors(next).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
// Use standard CORS for all other endpoints
|
|
standardCors(next).ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|
|
|
|
func WorkspaceAppCors(regex *regexp.Regexp, app appurl.ApplicationURL) func(next http.Handler) http.Handler {
|
|
return cors.Handler(cors.Options{
|
|
AllowOriginFunc: func(_ *http.Request, rawOrigin string) bool {
|
|
origin, err := url.Parse(rawOrigin)
|
|
if rawOrigin == "" || origin.Host == "" || err != nil {
|
|
return false
|
|
}
|
|
subdomain, ok := appurl.ExecuteHostnamePattern(regex, origin.Host)
|
|
if !ok {
|
|
return false
|
|
}
|
|
originApp, err := appurl.ParseSubdomainAppURL(subdomain)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return ok && originApp.Username == app.Username
|
|
},
|
|
AllowedMethods: []string{
|
|
http.MethodHead,
|
|
http.MethodGet,
|
|
http.MethodPost,
|
|
http.MethodPut,
|
|
http.MethodPatch,
|
|
http.MethodDelete,
|
|
},
|
|
AllowedHeaders: []string{"*"},
|
|
AllowCredentials: true,
|
|
})
|
|
}
|