Skip to content

Commit 96c83c7

Browse files
authored
Merge branch 'main' into stevenmasley/preview_errors
2 parents a87678d + 09c5055 commit 96c83c7

File tree

16 files changed

+1015
-4
lines changed

16 files changed

+1015
-4
lines changed

coderd/apidoc/docs.go

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,8 @@ func New(options *Options) *API {
914914

915915
// OAuth2 metadata endpoint for RFC 8414 discovery
916916
r.Get("/.well-known/oauth-authorization-server", api.oauth2AuthorizationServerMetadata)
917+
// OAuth2 protected resource metadata endpoint for RFC 9728 discovery
918+
r.Get("/.well-known/oauth-protected-resource", api.oauth2ProtectedResourceMetadata)
917919

918920
// OAuth2 linking routes do not make sense under the /api/v2 path. These are
919921
// for an external application to use Coder as an OAuth2 provider, not for

coderd/httpmw/apikey.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,31 @@ func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyCon
214214
return nil, nil, false
215215
}
216216

217+
// Add WWW-Authenticate header for 401/403 responses (RFC 6750)
218+
if code == http.StatusUnauthorized || code == http.StatusForbidden {
219+
var wwwAuth string
220+
221+
switch code {
222+
case http.StatusUnauthorized:
223+
// Map 401 to invalid_token with specific error descriptions
224+
switch {
225+
case strings.Contains(response.Message, "expired") || strings.Contains(response.Detail, "expired"):
226+
wwwAuth = `Bearer realm="coder", error="invalid_token", error_description="The access token has expired"`
227+
case strings.Contains(response.Message, "audience") || strings.Contains(response.Message, "mismatch"):
228+
wwwAuth = `Bearer realm="coder", error="invalid_token", error_description="The access token audience does not match this resource"`
229+
default:
230+
wwwAuth = `Bearer realm="coder", error="invalid_token", error_description="The access token is invalid"`
231+
}
232+
case http.StatusForbidden:
233+
// Map 403 to insufficient_scope per RFC 6750
234+
wwwAuth = `Bearer realm="coder", error="insufficient_scope", error_description="The request requires higher privileges than provided by the access token"`
235+
default:
236+
wwwAuth = `Bearer realm="coder"`
237+
}
238+
239+
rw.Header().Set("WWW-Authenticate", wwwAuth)
240+
}
241+
217242
httpapi.Write(ctx, rw, code, response)
218243
return nil, nil, false
219244
}
@@ -653,9 +678,14 @@ func UserRBACSubject(ctx context.Context, db database.Store, userID uuid.UUID, s
653678
// 1: The cookie
654679
// 2. The coder_session_token query parameter
655680
// 3. The custom auth header
681+
// 4. RFC 6750 Authorization: Bearer header
682+
// 5. RFC 6750 access_token query parameter
656683
//
657684
// API tokens for apps are read from workspaceapps/cookies.go.
658685
func APITokenFromRequest(r *http.Request) string {
686+
// Prioritize existing Coder custom authentication methods first
687+
// to maintain backward compatibility and existing behavior
688+
659689
cookie, err := r.Cookie(codersdk.SessionTokenCookie)
660690
if err == nil && cookie.Value != "" {
661691
return cookie.Value
@@ -671,6 +701,19 @@ func APITokenFromRequest(r *http.Request) string {
671701
return headerValue
672702
}
673703

704+
// RFC 6750 Bearer Token support (added as fallback methods)
705+
// Check Authorization: Bearer <token> header (case-insensitive per RFC 6750)
706+
authHeader := r.Header.Get("Authorization")
707+
if strings.HasPrefix(strings.ToLower(authHeader), "bearer ") {
708+
return authHeader[7:] // Skip "Bearer " (7 characters)
709+
}
710+
711+
// Check access_token query parameter
712+
accessToken := r.URL.Query().Get("access_token")
713+
if accessToken != "" {
714+
return accessToken
715+
}
716+
674717
return ""
675718
}
676719

coderd/httpmw/csrf.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ func CSRF(cookieCfg codersdk.HTTPCookieConfig) func(next http.Handler) http.Hand
102102
return true
103103
}
104104

105+
// RFC 6750 Bearer Token authentication is exempt from CSRF
106+
// as it uses custom headers that cannot be set by malicious sites
107+
if authHeader := r.Header.Get("Authorization"); strings.HasPrefix(strings.ToLower(authHeader), "bearer ") {
108+
return true
109+
}
110+
105111
// If the X-CSRF-TOKEN header is set, we can exempt the func if it's valid.
106112
// This is the CSRF check.
107113
sent := r.Header.Get("X-CSRF-TOKEN")

0 commit comments

Comments
 (0)