diff --git a/auth/auth.go b/auth/auth.go index 4f0393008..8da452787 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -12,7 +12,6 @@ import ( "net/http" "net/url" "os" - "strconv" "strings" "time" @@ -25,6 +24,7 @@ import ( "github.com/cortezaproject/corteza-server/auth/settings" "github.com/cortezaproject/corteza-server/pkg/actionlog" "github.com/cortezaproject/corteza-server/pkg/auth" + "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/pkg/locale" "github.com/cortezaproject/corteza-server/pkg/options" "github.com/cortezaproject/corteza-server/pkg/version" @@ -33,6 +33,7 @@ import ( "github.com/cortezaproject/corteza-server/system/types" "github.com/go-chi/chi/v5" oauth2def "github.com/go-oauth2/oauth2/v4" + "github.com/spf13/cast" "go.uber.org/zap" "golang.org/x/text/language" ) @@ -87,17 +88,10 @@ func New(ctx context.Context, log *zap.Logger, oa2m oauth2def.Manager, s store.S // this is a bit silly and a bad design of the oauth2 server lib // why do we need to keep on load the client?? var ( - clientID uint64 - client *types.AuthClient + client *types.AuthClient ) - clientID, err = strconv.ParseUint(id, 10, 64) - if err != nil { - return false, fmt.Errorf("could not authorize client: %w", err) - } - - client, err = store.LookupAuthClientByID(ctx, s, clientID) - if err != nil { + if client, err = clientLookup(ctx, s, id); err != nil { return false, fmt.Errorf("could not authorize client: %w", err) } @@ -113,17 +107,10 @@ func New(ctx context.Context, log *zap.Logger, oa2m oauth2def.Manager, s store.S // this is a bit silly and a bad design of the oauth2 server lib // why do we need to keep on load the client?? var ( - clientID uint64 - client *types.AuthClient + client *types.AuthClient ) - clientID, err = strconv.ParseUint(tgr.ClientID, 10, 64) - if err != nil { - return false, fmt.Errorf("could not authorize client: %w", err) - } - - client, err = store.LookupAuthClientByID(ctx, s, clientID) - if err != nil { + if client, err = clientLookup(ctx, s, tgr.ClientID); err != nil { return false, fmt.Errorf("could not authorize client: %w", err) } @@ -139,7 +126,7 @@ func New(ctx context.Context, log *zap.Logger, oa2m oauth2def.Manager, s store.S fieldsValue = make(map[string]interface{}) handlers.SubSplit(ti, fieldsValue) fieldsValue["refresh_token_expires_in"] = int(ti.GetRefreshExpiresIn() / time.Second) - if err = handlers.Profile(ctx, ti, fieldsValue); err != nil { + if err = userProfile(ctx, s, ti, fieldsValue); err != nil { log.Error("failed to add profile data", zap.Error(err)) } @@ -464,3 +451,43 @@ func (svc service) WellKnownOpenIDConfiguration() http.HandlerFunc { w.Header().Set("Content-Type", "application/json") } } + +func clientLookup(ctx context.Context, s store.AuthClients, identifier interface{}) (*types.AuthClient, error) { + if id := cast.ToUint64(identifier); id > 0 { + return store.LookupAuthClientByID(ctx, s, id) + } else if h := cast.ToString(identifier); handle.IsValid(h) { + return store.LookupAuthClientByHandle(ctx, s, h) + } else { + return nil, systemService.AuthClientErrInvalidID() + } +} + +// Profile fills map with user's data +// +// If scope supports it (contains "profile") user is loaded and +// map is filled with username (handle), email and name +func userProfile(ctx context.Context, s store.Users, ti oauth2def.TokenInfo, data map[string]interface{}) error { + if !auth.CheckScope(ti.GetScope(), "profile") { + return nil + } + + userID, _ := auth.ExtractFromSubClaim(ti.GetUserID()) + if userID == 0 { + return fmt.Errorf("invalid user ID in 'sub' claim") + } + + user, err := store.LookupUserByID(ctx, s, userID) + if err != nil { + return err + } + + data["handle"] = user.Handle + data["name"] = user.Name + data["email"] = user.Email + + if user.Meta != nil && user.Meta.PreferredLanguage != "" { + data["preferred_language"] = user.Meta.PreferredLanguage + } + + return nil +} diff --git a/auth/client.go b/auth/client.go index d3b4a71a0..12a420058 100644 --- a/auth/client.go +++ b/auth/client.go @@ -2,6 +2,7 @@ package auth import ( "context" + "github.com/cortezaproject/corteza-server/store" "github.com/cortezaproject/corteza-server/system/types" ) @@ -15,8 +16,12 @@ type ( } ) -func (svc clientService) LookupByID(ctx context.Context, clientID uint64) (*types.AuthClient, error) { - return store.LookupAuthClientByID(ctx, svc.store, clientID) +func (svc clientService) Lookup(ctx context.Context, identifier interface{}) (*types.AuthClient, error) { + return clientLookup(ctx, svc.store, identifier) +} + +func (svc clientService) LookupByHandle(ctx context.Context, handle string) (*types.AuthClient, error) { + return store.LookupAuthClientByHandle(ctx, svc.store, handle) } func (svc clientService) Confirmed(ctx context.Context, userID uint64) (types.AuthConfirmedClientSet, error) { diff --git a/auth/external/goth.go b/auth/external/goth.go index 0d5b6ce88..6aab3e6d3 100644 --- a/auth/external/goth.go +++ b/auth/external/goth.go @@ -52,12 +52,12 @@ func SetupGothProviders(log *zap.Logger, redirectUrl string, ep ...settings.Prov wellKnown := strings.TrimSuffix(pc.IssuerUrl, "/") + WellKnown - scope := pc.Scope + scope := strings.Split(pc.Scope, " ") if len(scope) == 0 { - scope = "email" + scope = append(scope, "email") } - if provider, err = openidConnect.New(pc.Key, pc.Secret, redirect, wellKnown, scope); err != nil { + if provider, err = openidConnect.New(pc.Key, pc.Secret, redirect, wellKnown, scope...); err != nil { log.Error("failed to discover OIDC provider", zap.Error(err), zap.String("well-known", wellKnown)) continue } else { diff --git a/auth/handlers/handle_authorized-clients.go b/auth/handlers/handle_authorized-clients.go index 8cd353d89..1e7c7fb28 100644 --- a/auth/handlers/handle_authorized-clients.go +++ b/auth/handlers/handle_authorized-clients.go @@ -83,7 +83,7 @@ func (h *AuthHandlers) getAuthorizedClients(req *request.AuthReq) (ss authClient ss = make(authClients, 0, len(set)) for i := range set { - client, err = h.ClientService.LookupByID(req.Context(), set[i].ClientID) + client, err = h.ClientService.Lookup(req.Context(), set[i].ClientID) if errors.IsNotFound(err) { continue } diff --git a/auth/handlers/handle_external.go b/auth/handlers/handle_external.go index 4639c18d2..d3e655414 100644 --- a/auth/handlers/handle_external.go +++ b/auth/handlers/handle_external.go @@ -78,9 +78,7 @@ func (h AuthHandlers) handleSuccessfulExternalAuth(w http.ResponseWriter, r *htt })(w, r) } -func (h AuthHandlers) handleFailedExternalAuth(w http.ResponseWriter, r *http.Request, err error) { - //provider := chi.URLParam(r, "provider") - +func (h AuthHandlers) handleFailedExternalAuth(w http.ResponseWriter, _ *http.Request, err error) { if strings.Contains(err.Error(), "Error processing your OAuth request: Invalid oauth_verifier parameter") { // Just take user through the same loop again w.Header().Set("Location", GetLinks().Profile) @@ -88,8 +86,8 @@ func (h AuthHandlers) handleFailedExternalAuth(w http.ResponseWriter, r *http.Re return } - fmt.Fprintf(w, "SSO Error: %v", err.Error()) w.WriteHeader(http.StatusOK) + _, _ = fmt.Fprintf(w, "SSO Error: %v", err.Error()) } func beginUserAuth(w http.ResponseWriter, r *http.Request, eh external.ExternalAuthHandler) { diff --git a/auth/handlers/handle_oauth2.go b/auth/handlers/handle_oauth2.go index 33b124e34..fa7c177fd 100644 --- a/auth/handlers/handle_oauth2.go +++ b/auth/handlers/handle_oauth2.go @@ -11,9 +11,11 @@ import ( "strconv" "strings" + "github.com/lestrrat-go/jwx/jwa" + "github.com/lestrrat-go/jwx/jwk" + "github.com/go-chi/jwtauth" oauth2errors "github.com/go-oauth2/oauth2/v4/errors" - "github.com/lestrrat-go/jwx/jwk" "github.com/lestrrat-go/jwx/jwt" "github.com/cortezaproject/corteza-server/auth/oauth2" @@ -58,6 +60,9 @@ func (h AuthHandlers) oauth2Authorize(req *request.AuthReq) (err error) { // No client validation is done at this point; // first, see if user is able to authenticate. request.SetOauth2Client(req.Session, client) + + // ensure we're dealing with client ID in case someone used handle + req.Request.Form.Set("client_id", strconv.FormatUint(client.ID, 10)) } // set to -1 to make sure wrapping request handler @@ -161,7 +166,6 @@ func (h AuthHandlers) oauth2Token(req *request.AuthReq) (err error) { req.Status = -1 client, err := h.loadRequestedClient(req) - if err != nil { return h.tokenError(req.Response, err) } @@ -318,9 +322,8 @@ func (h AuthHandlers) verifyDefaultClient() error { func (h AuthHandlers) loadRequestedClient(req *request.AuthReq) (client *types.AuthClient, err error) { return client, func() (err error) { var ( - id string - clientID uint64 - found bool + id string + found bool ) if id, _, found = req.Request.BasicAuth(); !found { @@ -331,31 +334,7 @@ func (h AuthHandlers) loadRequestedClient(req *request.AuthReq) (client *types.A } } - h.Log.Debug("loading client", zap.String("info", id)) - - if clientID, err = strconv.ParseUint(id, 10, 64); err != nil { - return errors.InvalidData("failed to parse client ID from params: %v", err) - - } else if clientID == 0 { - return errors.InvalidData("invalid client ID") - } - - if client = request.GetOauth2Client(req.Session); client != nil { - h.Log.Debug("client loaded from session", zap.Uint64("ID", client.ID)) - - // ensure that session holds the right client and - // not some leftover from a previous flow - if client.ID != clientID { - h.Log.Debug("stale client found in session") - - // cleanup leftovers - client = nil - } else { - return - } - } - - client, err = h.ClientService.LookupByID(req.Context(), clientID) + client, err = h.ClientService.Lookup(req.Context(), id) if err != nil { return fmt.Errorf("invalid client: %w", err) } @@ -370,6 +349,8 @@ func (h AuthHandlers) handleTokenRequest(req *request.AuthReq, client *types.Aut r = req.Request w = req.Response ctx = req.Context() + + user *types.User ) req.Status = -1 @@ -378,7 +359,7 @@ func (h AuthHandlers) handleTokenRequest(req *request.AuthReq, client *types.Aut return h.tokenError(w, fmt.Errorf("invalid client: %w", err)) } - // add client to context so we can reach it from client store via context.Value() fn + // add client to context: we can reach it from client store via context.Value() fn // this way we work around the limitations we have with the oauth2 lib. ctx = context.WithValue(ctx, &oauth2.ContextClientStore{}, client) r = req.Request.Clone(ctx) @@ -388,39 +369,71 @@ func (h AuthHandlers) handleTokenRequest(req *request.AuthReq, client *types.Aut return h.tokenError(w, err) } - if gt == oauth2def.ClientCredentials { - // Authenticated with client credentials! - // - // We'll use info from client security - if client.Security == nil || client.Security.ImpersonateUser == 0 { - return h.tokenError(w, errors.Internal("auth client security configuration invalid")) - } - - tgr.UserID = strings.Join(append( - []string{fmt.Sprintf("%d", client.Security.ImpersonateUser)}, - client.Security.ForcedRoles..., - ), " ") - } - ti, err := h.OAuth2.GetAccessToken(ctx, gt, tgr) if err != nil { return h.tokenError(w, err) } + suCtx := auth.SetIdentityToContext(ctx, auth.ServiceUser()) + + switch gt { + case oauth2def.ClientCredentials: + // Authenticated with client credentials! + + // First, validate client's security settings + if client.Security == nil || client.Security.ImpersonateUser == 0 { + return h.tokenError(w, errors.Internal("auth client security configuration invalid")) + } + + // Load the user + if user, err = h.UserService.FindByAny(suCtx, client.Security.ImpersonateUser); err != nil { + return h.tokenError(w, fmt.Errorf("could not generate token for impersonated user: %v", err)) + } + + case oauth2def.AuthorizationCode, oauth2def.Refreshing: + userID := ti.GetUserID() + if i := strings.Index(ti.GetUserID(), " "); i > 0 { + // userID field from the token could contain encoded roles + // @todo investigate if role-encoding into user-id field is still needed? + userID = userID[:i] + } + + if user, err = h.UserService.FindByAny(suCtx, userID); err != nil { + return h.tokenError(w, fmt.Errorf("could not generate token: %v", err)) + } + + default: + return fmt.Errorf("unsupported oauth2 grant type: %v", gt) + } + var ( - user = req.AuthUser.User.Clone() signed []byte + scope = strings.Split(ti.GetScope(), " ") ) - signed, err = auth.JWT().Sign(ti.GetAccess(), user, client.ID, strings.Split(ti.GetScope(), " ")...) + signed, err = auth.JWT().Sign(ti.GetAccess(), user, client.ID, scope...) + if err != nil { + return h.tokenError(w, err) + } + ti.SetAccess(string(signed)) - return token(w, h.OAuth2.GetTokenData(ti), nil) + response := h.OAuth2.GetTokenData(ti) + + if strings.Contains(client.Scope, "openid") { + var idToken []byte + if idToken, err = generateIdToken(user, client, ti, h.Opt.BaseURL); err != nil { + return h.tokenError(w, err) + } + response["id_token"] = string(idToken) + } + + return writeResponse(w, response, nil) } func (h AuthHandlers) tokenError(w http.ResponseWriter, err error) error { data, statusCode, header := h.OAuth2.GetErrorData(err) - return token(w, data, header, statusCode) + return writeResponse(w, data, header, statusCode) } func (h AuthHandlers) oauth2PublicKeys(w http.ResponseWriter, r *http.Request) { @@ -468,43 +481,38 @@ func SubSplit(ti oauth2def.TokenInfo, data map[string]interface{}) { } } -// Profile fills map with user's data -// -// If scope supports it (contains "profile") user is loaded and -// map is filled with username (handle), email and name -func Profile(ctx context.Context, ti oauth2def.TokenInfo, data map[string]interface{}) error { - if !auth.CheckScope(ti.GetScope(), "profile") { - return nil +// Generates ID token that is part of OIDC flow for doing corteza-to-corteza auth +func generateIdToken(user *types.User, client *types.AuthClient, ti oauth2def.TokenInfo, baseURL string) (_ []byte, err error) { + token := jwt.New() + if err = token.Set(jwt.IssuerKey, baseURL); err != nil { + return } - userID, roles := auth.ExtractFromSubClaim(ti.GetUserID()) - if userID == 0 { - return fmt.Errorf("invalid user ID in 'sub' claim") + // we do not know what the admin used for client key value + // on the receiving end, so we'll encode both, + // client's ID, and it's handle + aud := []string{strconv.FormatUint(client.ID, 10)} + if len(client.Handle) > 0 { + aud = append(aud, client.Handle) } - user, err := systemService.DefaultUser.FindByID( - // inject ad-hoc identity into context so that user service is aware who is - // doing the lookup - auth.SetIdentityToContext(ctx, auth.Authenticated(userID, roles...)), - userID, - ) - - if err != nil { - return err + if err = token.Set("aud", aud); err != nil { + return + } + if err = token.Set("user_id", strconv.FormatUint(user.ID, 10)); err != nil { + return + } + if err = token.Set("email", user.Email); err != nil { + return + } + if err = token.Set(jwt.ExpirationKey, now().Add(ti.GetAccessExpiresIn()).Unix()); err != nil { + return } - data["handle"] = user.Handle - data["name"] = user.Name - data["email"] = user.Email - - if user.Meta != nil && user.Meta.PreferredLanguage != "" { - data["preferred_language"] = user.Meta.PreferredLanguage - } - - return nil + return jwt.Sign(token, jwa.HS512, []byte(client.Secret)) } -func token(w http.ResponseWriter, data map[string]interface{}, header http.Header, statusCode ...int) error { +func writeResponse(w http.ResponseWriter, data map[string]interface{}, header http.Header, statusCode ...int) error { w.Header().Set("Content-Type", "application/json;charset=UTF-8") w.Header().Set("Cache-Control", "no-store") w.Header().Set("Pragma", "no-cache") diff --git a/auth/handlers/handle_oauth2_test.go b/auth/handlers/handle_oauth2_test.go index b1b878494..e41ecabfa 100644 --- a/auth/handlers/handle_oauth2_test.go +++ b/auth/handlers/handle_oauth2_test.go @@ -8,6 +8,8 @@ import ( "github.com/cortezaproject/corteza-server/auth/request" "github.com/cortezaproject/corteza-server/auth/settings" + "github.com/cortezaproject/corteza-server/system/types" + oauth2models "github.com/go-oauth2/oauth2/v4/models" "github.com/stretchr/testify/require" "go.uber.org/zap" ) @@ -125,3 +127,21 @@ func Test_oauth2AuthorizeSuccessSetParams(t *testing.T) { rq.Equal(-1, authReq.Status) rq.Equal(nil, authReq.Data["error"]) } + +func Test_generateIdToken(t *testing.T) { + var ( + req = require.New(t) + ) + + signed, err := generateIdToken( + &types.User{}, + &types.AuthClient{ + Secret: "correct horse battery stable", + }, + &oauth2models.Token{}, + "http://cortezaproject.org", + ) + + req.NoError(err) + req.NotEmpty(signed) +} diff --git a/auth/handlers/handler.go b/auth/handlers/handler.go index cf902e113..5df701a6d 100644 --- a/auth/handlers/handler.go +++ b/auth/handlers/handler.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" "strings" + "time" "github.com/cortezaproject/corteza-server/auth/external" "github.com/cortezaproject/corteza-server/auth/request" @@ -51,11 +52,12 @@ type ( } userService interface { + FindByAny(ctx context.Context, identifier interface{}) (*types.User, error) Update(context.Context, *types.User) (*types.User, error) } clientService interface { - LookupByID(context.Context, uint64) (*types.AuthClient, error) + Lookup(context.Context, interface{}) (*types.AuthClient, error) Confirmed(context.Context, uint64) (types.AuthConfirmedClientSet, error) Revoke(ctx context.Context, userID, clientID uint64) error } @@ -136,6 +138,14 @@ const ( TmplInternalError = "error-internal.html.tpl" ) +var ( + // wrapper around time.Now() that will aid service testing + now = func() *time.Time { + c := time.Now() + return &c + } +) + func init() { gob.Register(&types.User{}) gob.Register(&types.AuthClient{}) diff --git a/auth/oauth2/client.go b/auth/oauth2/client.go index a6715fb89..5bcf5eec6 100644 --- a/auth/oauth2/client.go +++ b/auth/oauth2/client.go @@ -19,8 +19,7 @@ var _ oauth2.ClientStore = &ContextClientStore{} // // This requires that client is put in context before oauth2 procedures are executed! func (s ContextClientStore) GetByID(ctx context.Context, id string) (oauth2.ClientInfo, error) { - return &clientInfo{&types.AuthClient{}}, nil - //return &clientInfo{ctx.Value(&ContextClientStore{}).(*types.AuthClient)}, nil + return &clientInfo{ctx.Value(&ContextClientStore{}).(*types.AuthClient)}, nil } type ( diff --git a/auth/oauth2/corteza_token_store.go b/auth/oauth2/corteza_token_store.go index 851f9b343..0a2830817 100644 --- a/auth/oauth2/corteza_token_store.go +++ b/auth/oauth2/corteza_token_store.go @@ -65,36 +65,6 @@ func (c CortezaTokenStore) Create(ctx context.Context, info oauth2.TokenInfo) (e return } - // this is oauth2 specific go-code and there is no - // need for it to be moved to MakeAuthStructs fn in auth pkg - if code := info.GetCode(); code != "" { - oa2t.Code = code - } else { - // When creating non-access-code tokens, - // we need to overwrite expiration time - // with custom values for access or refresh token - oa2t.Access = info.GetAccess() - oa2t.ExpiresAt = info.GetAccessCreateAt().Add(info.GetAccessExpiresIn()) - - if refresh := info.GetRefresh(); refresh != "" { - oa2t.Refresh = info.GetRefresh() - oa2t.ExpiresAt = info.GetRefreshCreateAt().Add(info.GetRefreshExpiresIn()) - } - } - - //if oa2t.ClientID, err = strconv.ParseUint(info.GetClientID(), 10, 64); err != nil { - // return fmt.Errorf("could not parse client ID from token info: %w", err) - //} - - //if info.GetUserID() != "" { - // if oa2t.UserID, _ = auth.ExtractFromSubClaim(info.GetUserID()); oa2t.UserID == 0 { - // // UserID stores collection of IDs: user's ID and set of all roles user is member of - // return fmt.Errorf("could not parse user ID from token info") - // } - //} - // - //// copy user id to auth client confirmation - //acc.UserID = oa2t.UserID if err = store.UpsertAuthConfirmedClient(ctx, c.Store, acc); err != nil { return } @@ -169,7 +139,7 @@ func (c CortezaTokenStore) GetByRefresh(ctx context.Context, refresh string) (oa return internal, t.Data.Unmarshal(internal) } -func makeAuthStructs(ctx context.Context, jwtID, userID, clientID uint64, data oauth2.TokenInfo, expiresAt time.Duration) (oa2t *types.AuthOa2token, acc *types.AuthConfirmedClient, err error) { +func makeAuthStructs(ctx context.Context, jwtID, userID, clientID uint64, info oauth2.TokenInfo, expiresAt time.Duration) (oa2t *types.AuthOa2token, acc *types.AuthConfirmedClient, err error) { var ( eti = auth.GetExtraReqInfoFromContext(ctx) createdAt = time.Now().Round(time.Second) @@ -191,9 +161,24 @@ func makeAuthStructs(ctx context.Context, jwtID, userID, clientID uint64, data o ConfirmedAt: createdAt, } - if oa2t.Data, err = json.Marshal(data); err != nil { + if oa2t.Data, err = json.Marshal(info); err != nil { return nil, nil, err } + if code := info.GetCode(); code != "" { + oa2t.Code = code + } else { + // When creating non-access-code tokens, + // we need to overwrite expiration time + // with custom values for access or refresh token + oa2t.Access = info.GetAccess() + oa2t.ExpiresAt = info.GetAccessCreateAt().Add(info.GetAccessExpiresIn()) + + if refresh := info.GetRefresh(); refresh != "" { + oa2t.Refresh = info.GetRefresh() + oa2t.ExpiresAt = info.GetRefreshCreateAt().Add(info.GetRefreshExpiresIn()) + } + } + return } diff --git a/auth/oauth2/oauth2.go b/auth/oauth2/oauth2.go index 28d878fff..400e4a472 100644 --- a/auth/oauth2/oauth2.go +++ b/auth/oauth2/oauth2.go @@ -1,10 +1,14 @@ package oauth2 import ( + "fmt" + "net/http" "strings" + "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/pkg/logger" "github.com/cortezaproject/corteza-server/pkg/options" + "github.com/cortezaproject/corteza-server/pkg/payload" "github.com/go-oauth2/oauth2/v4" "github.com/go-oauth2/oauth2/v4/errors" "github.com/go-oauth2/oauth2/v4/manage" @@ -73,8 +77,6 @@ func NewServer(manager oauth2.Manager) *server.Server { AllowedGrantTypes: []oauth2.GrantType{ oauth2.AuthorizationCode, oauth2.Refreshing, - // before enabling ClientCredentials grant type, we need to know how to modify released token - // using client's security info; how to enforce impersonated user and his roles. oauth2.ClientCredentials, }, AllowedCodeChallengeMethods: []oauth2.CodeChallengeMethod{ @@ -83,6 +85,32 @@ func NewServer(manager oauth2.Manager) *server.Server { }, }, manager) + srv.ClientInfoHandler = func(r *http.Request) (clientID, clientSecret string, err error) { + // check in basic handler first + clientID, clientSecret, err = server.ClientBasicHandler(r) + + if clientID == "" && clientSecret == "" { + //error or no error, if ID & secret are empty, + // check the form handler + clientID, clientSecret, err = server.ClientFormHandler(r) + } + + // just in case, when client's handle is used instead of the ID + // preload it here + if id := payload.ParseUint64(clientID); id == 0 && handle.IsValid(clientID) { + var client oauth2.ClientInfo + client, err = manager.GetClient(r.Context(), clientID) + if err != nil { + err = fmt.Errorf("could not resolve client info: %v", err) + return + } + + clientID = client.GetID() + } + + return + } + srv.SetInternalErrorHandler(func(err error) (re *errors.Response) { return errors.NewResponse(err, 500) }) diff --git a/system/service/user.go b/system/service/user.go index 958b57637..726aee85d 100644 --- a/system/service/user.go +++ b/system/service/user.go @@ -199,12 +199,6 @@ func (svc user) FindByHandle(ctx context.Context, handle string) (u *types.User, } // FindByAny finds user by given identifier (context, id, handle, email) -// -// This function goes against the context anti (!!!) pattern we're using -// (and trying to get rid of) -// -// Main reason to push ctx here as the 1st arg is allow (simple) interface definition -// in the consumers that reside under the pkg/ func (svc user) FindByAny(ctx context.Context, identifier interface{}) (u *types.User, err error) { if ctx, ok := identifier.(context.Context); ok { identifier = internalAuth.GetIdentityFromContext(ctx).Identity()