Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions internal/clients/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package clients
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/url"
"strings"
Expand Down Expand Up @@ -211,10 +212,12 @@ func (service *Service) FetchClientHandler(w http.ResponseWriter, r *http.Reques
id := mux.Vars(r)["clientId"]
result, err := service.cs.GetClient(id)
if err != nil {
sentry.CaptureException(err)
status := http.StatusInternalServerError
if err == gorm.ErrRecordNotFound {
if errors.Is(err, gorm.ErrRecordNotFound) {
// Unknown client_id — a client error, not a server fault.
status = http.StatusNotFound
} else {
sentry.CaptureException(err)
}
http.Error(w, err.Error(), status)
return
Expand Down
8 changes: 8 additions & 0 deletions internal/gateway/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ type OriginServer struct {
}

func (origin *OriginServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()

//check authorization
token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")

Expand All @@ -43,6 +45,12 @@ func (origin *OriginServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

tokenInfo, err := origin.CheckTokenFunc(r.Context(), token)

// Client has aborted the request
if ctx.Err() == context.Canceled {
return
}

if err != nil {
if status, found := errorResponses[err.Error()]; found {
writeErrorResponse(w, err.Error(), status)
Expand Down
18 changes: 13 additions & 5 deletions internal/tokens/oauth.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package tokens

import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"

"github.com/getsentry/sentry-go"
"github.com/go-oauth2/oauth2/v4"
oauthErrors "github.com/go-oauth2/oauth2/v4/errors"
"github.com/go-oauth2/oauth2/v4/manage"
"github.com/go-oauth2/oauth2/v4/server"
"github.com/sirupsen/logrus"
)

func initOauthServer(conf Config, cs oauth2.ClientStore, ts oauth2.TokenStore) (srv *server.Server, err error) {
Expand Down Expand Up @@ -99,9 +99,17 @@ func checkRedirectUriDomain(baseURI, redirectURI string) error {
clientHost := parsedClientUri.Host
redirectHost := parsedRedirect.Host
if parsedClientUri.Scheme != parsedRedirect.Scheme || !isUriValid(clientHost, redirectHost) {
err = fmt.Errorf("wrong redirect uri, provided: [ scheme: %s, host: %s ], expected: [ scheme: %s, host: %s ]", parsedRedirect.Scheme, parsedRedirect.Host, parsedClientUri.Scheme, parsedClientUri.Host)
sentry.CaptureException(err)
return err
// Caller supplied a redirect URI that doesn't match the registered one — a
// client misconfiguration, not a server fault. Log only scheme/host (not the
// full client-supplied URIs) and return a standard OAuth error so it isn't
// reported to Sentry downstream.
logrus.WithFields(logrus.Fields{
"provided_scheme": parsedRedirect.Scheme,
"provided_host": redirectHost,
"expected_scheme": parsedClientUri.Scheme,
"expected_host": clientHost,
}).Warn("rejected request with mismatched redirect uri")
return oauthErrors.ErrInvalidRequest
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
return nil
}
34 changes: 25 additions & 9 deletions internal/tokens/tokens.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package tokens

import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httputil"
Expand Down Expand Up @@ -89,7 +91,12 @@ func NewService(cs oauth2.ClientStore, ts oauth2.TokenStore, scopes map[string]s
func (svc *service) AuthorizationHandler(w http.ResponseWriter, r *http.Request) {
err := svc.OauthServer.HandleAuthorizeRequest(w, r)
if err != nil {
sentry.CaptureException(err)
// Known OAuth protocol errors with a 4xx status code are client mistakes,
// not server faults. Report everything else (5xx OAuth errors and any
// unrecognised error, e.g. a DB failure).
if code, ok := oauthErrors.StatusCodes[err]; !ok || code >= 500 {
sentry.CaptureException(err)
}
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
Expand All @@ -103,7 +110,9 @@ func (svc *service) TokenHandler(w http.ResponseWriter, r *http.Request) {
logrus.WithField("token_request", fmt.Sprintf("%q", string(dump))).
WithError(err).
Error("error validating token request")
sentry.CaptureException(err)
if code, ok := oauthErrors.StatusCodes[err]; !ok || code >= 500 {
sentry.CaptureException(err)
}
svc.tokenError(w, err)
return
}
Expand Down Expand Up @@ -194,8 +203,12 @@ func (svc *service) token(w http.ResponseWriter, data map[string]interface{}, he
}

func (svc *service) InternalErrorHandler(err error) (re *oauthErrors.Response) {
//workaround to not show "sql: no rows in result set" to user
sentry.CaptureException(err)
// This handler only receives errors the OAuth library couldn't classify, i.e.
// genuine server faults — except for context.Canceled, which just means the
// client aborted the request, so don't report that to Sentry.
if !errors.Is(err, context.Canceled) {
sentry.CaptureException(err)
}
description := oauthErrors.Descriptions[err]
statusCode := oauthErrors.StatusCodes[err]
if description != "" && statusCode != 0 {
Expand All @@ -219,21 +232,24 @@ func (svc *service) InternalErrorHandler(err error) (re *oauthErrors.Response) {
}

func preRedirectErrorHandler(w http.ResponseWriter, r *server.AuthorizeRequest, err error) error {
// The error is returned up to AuthorizationHandler, which decides whether to
// report it to Sentry, so only log it here to avoid a duplicate capture.
logrus.WithField("Authorize request", r).Error(err)
sentry.CaptureException(err)
return err
}

func (svc *service) AuthorizeScopeHandler(w http.ResponseWriter, r *http.Request) (scope string, err error) {
requestedScope := r.FormValue("scope")
if requestedScope == "" {
return "", fmt.Errorf("empty scope is not allowed")
logrus.Warn("rejected request with empty scope")
return "", oauthErrors.ErrInvalidScope
}
for _, scope := range strings.Split(requestedScope, " ") {
if _, found := svc.scopes[scope]; !found {
err = fmt.Errorf("scope not allowed: %s", scope)
sentry.CaptureException(err)
return "", err
// Caller requested an unknown scope — a client error, not a server
// fault. Return the standard OAuth error so it isn't reported to Sentry.
logrus.WithField("scope", scope).Warn("rejected request with disallowed scope")
return "", oauthErrors.ErrInvalidScope
}
}
return requestedScope, nil
Expand Down
Loading