diff --git a/.schema/api.swagger.json b/.schema/api.swagger.json index 9a2d60a08f9..f28a10cbd52 100755 --- a/.schema/api.swagger.json +++ b/.schema/api.swagger.json @@ -1740,6 +1740,48 @@ } } }, + "/oauth2/tokens": { + "delete": { + "description": "This endpoint deletes OAuth2 access tokens issued for a client from the database", + "consumes": [ + "application/json" + ], + "schemes": [ + "http", + "https" + ], + "tags": [ + "admin" + ], + "summary": "Delete OAuth2 Access Tokens from a client", + "operationId": "deleteOAuth2Token", + "parameters": [ + { + "type": "string", + "name": "client_id", + "in": "query", + "required": true + } + ], + "responses": { + "204": { + "description": "Empty responses are sent when, for example, resources are deleted. The HTTP status code for empty responses is\ntypically 201." + }, + "401": { + "description": "genericError", + "schema": { + "$ref": "#/definitions/genericError" + } + }, + "500": { + "description": "genericError", + "schema": { + "$ref": "#/definitions/genericError" + } + } + } + } + }, "/userinfo": { "get": { "security": [ diff --git a/cmd/cli/handler_token.go b/cmd/cli/handler_token.go index 7300dad3fe6..38e20916ba9 100644 --- a/cmd/cli/handler_token.go +++ b/cmd/cli/handler_token.go @@ -72,3 +72,17 @@ func (h *TokenHandler) FlushTokens(cmd *cobra.Command, args []string) { cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err)) fmt.Println("Successfully flushed inactive access tokens") } + +func (h *TokenHandler) DeleteToken(cmd *cobra.Command, args []string) { + handler := configureClient(cmd) + clientID := flagx.MustGetString(cmd, "client-id") + if clientID == "" { + cmdx.Fatalf(`%s + +Please provide a Client ID using flags --client-id, or environment variables OAUTH2_CLIENT_ID +`, cmd.UsageString()) + } + _, err := handler.Admin.DeleteOAuth2Token(admin.NewDeleteOAuth2TokenParams().WithClientID(clientID)) + cmdx.Must(err, "The request failed with the following error message:\n%s", formatSwaggerError(err)) + fmt.Printf("Successfully deleted access tokens for client %s\n", clientID) +} diff --git a/cmd/token_delete.go b/cmd/token_delete.go new file mode 100644 index 00000000000..283277ba9b3 --- /dev/null +++ b/cmd/token_delete.go @@ -0,0 +1,41 @@ +/* + * Copyright © 2015-2018 Aeneas Rekkas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @author Aeneas Rekkas + * @copyright 2015-2018 Aeneas Rekkas + * @license Apache-2.0 + */ + +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// deleteCmd represents the delete command +var tokenDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Deletes access tokens of a client", + Run: cmdHandler.Token.DeleteToken, +} + +func init() { + tokenCmd.AddCommand(tokenDeleteCmd) + tokenDeleteCmd.Flags().String("client-id", os.Getenv("OAUTH2_CLIENT_ID"), "Use the provided OAuth 2.0 Client ID, defaults to environment variable OAUTH2_CLIENT_ID") + tokenDeleteCmd.Flags().String("endpoint", os.Getenv("HYDRA_URL"), "Set the URL where ORY Hydra is hosted, defaults to environment variable HYDRA_URL") + tokenDeleteCmd.Flags().String("access-token", os.Getenv("OAUTH2_ACCESS_TOKEN"), "Set an access token to be used in the Authorization header, defaults to environment variable OAUTH2_ACCESS_TOKEN") +} diff --git a/internal/httpclient/client/admin/admin_client.go b/internal/httpclient/client/admin/admin_client.go index ed89cbc934a..2df9e62a4a7 100644 --- a/internal/httpclient/client/admin/admin_client.go +++ b/internal/httpclient/client/admin/admin_client.go @@ -43,6 +43,8 @@ type ClientService interface { DeleteOAuth2Client(params *DeleteOAuth2ClientParams) (*DeleteOAuth2ClientNoContent, error) + DeleteOAuth2Token(params *DeleteOAuth2TokenParams) (*DeleteOAuth2TokenNoContent, error) + FlushInactiveOAuth2Tokens(params *FlushInactiveOAuth2TokensParams) (*FlushInactiveOAuth2TokensNoContent, error) GetConsentRequest(params *GetConsentRequestParams) (*GetConsentRequestOK, error) @@ -416,6 +418,42 @@ func (a *Client) DeleteOAuth2Client(params *DeleteOAuth2ClientParams) (*DeleteOA panic(msg) } +/* + DeleteOAuth2Token deletes o auth2 access tokens from a client + + This endpoint deletes OAuth2 access tokens issued for a client from the database +*/ +func (a *Client) DeleteOAuth2Token(params *DeleteOAuth2TokenParams) (*DeleteOAuth2TokenNoContent, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewDeleteOAuth2TokenParams() + } + + result, err := a.transport.Submit(&runtime.ClientOperation{ + ID: "deleteOAuth2Token", + Method: "DELETE", + PathPattern: "/oauth2/tokens", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http", "https"}, + Params: params, + Reader: &DeleteOAuth2TokenReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + }) + if err != nil { + return nil, err + } + success, ok := result.(*DeleteOAuth2TokenNoContent) + if ok { + return success, nil + } + // unexpected success response + // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue + msg := fmt.Sprintf("unexpected success response for deleteOAuth2Token: API contract not enforced by server. Client expected to get an error, but got: %T", result) + panic(msg) +} + /* FlushInactiveOAuth2Tokens flushes expired o auth2 access tokens diff --git a/internal/httpclient/client/admin/delete_o_auth2_token_parameters.go b/internal/httpclient/client/admin/delete_o_auth2_token_parameters.go new file mode 100644 index 00000000000..ae926e1d885 --- /dev/null +++ b/internal/httpclient/client/admin/delete_o_auth2_token_parameters.go @@ -0,0 +1,136 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package admin + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewDeleteOAuth2TokenParams creates a new DeleteOAuth2TokenParams object +// with the default values initialized. +func NewDeleteOAuth2TokenParams() *DeleteOAuth2TokenParams { + var () + return &DeleteOAuth2TokenParams{ + + timeout: cr.DefaultTimeout, + } +} + +// NewDeleteOAuth2TokenParamsWithTimeout creates a new DeleteOAuth2TokenParams object +// with the default values initialized, and the ability to set a timeout on a request +func NewDeleteOAuth2TokenParamsWithTimeout(timeout time.Duration) *DeleteOAuth2TokenParams { + var () + return &DeleteOAuth2TokenParams{ + + timeout: timeout, + } +} + +// NewDeleteOAuth2TokenParamsWithContext creates a new DeleteOAuth2TokenParams object +// with the default values initialized, and the ability to set a context for a request +func NewDeleteOAuth2TokenParamsWithContext(ctx context.Context) *DeleteOAuth2TokenParams { + var () + return &DeleteOAuth2TokenParams{ + + Context: ctx, + } +} + +// NewDeleteOAuth2TokenParamsWithHTTPClient creates a new DeleteOAuth2TokenParams object +// with the default values initialized, and the ability to set a custom HTTPClient for a request +func NewDeleteOAuth2TokenParamsWithHTTPClient(client *http.Client) *DeleteOAuth2TokenParams { + var () + return &DeleteOAuth2TokenParams{ + HTTPClient: client, + } +} + +/*DeleteOAuth2TokenParams contains all the parameters to send to the API endpoint +for the delete o auth2 token operation typically these are written to a http.Request +*/ +type DeleteOAuth2TokenParams struct { + + /*ClientID*/ + ClientID string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithTimeout adds the timeout to the delete o auth2 token params +func (o *DeleteOAuth2TokenParams) WithTimeout(timeout time.Duration) *DeleteOAuth2TokenParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the delete o auth2 token params +func (o *DeleteOAuth2TokenParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the delete o auth2 token params +func (o *DeleteOAuth2TokenParams) WithContext(ctx context.Context) *DeleteOAuth2TokenParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the delete o auth2 token params +func (o *DeleteOAuth2TokenParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the delete o auth2 token params +func (o *DeleteOAuth2TokenParams) WithHTTPClient(client *http.Client) *DeleteOAuth2TokenParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the delete o auth2 token params +func (o *DeleteOAuth2TokenParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithClientID adds the clientID to the delete o auth2 token params +func (o *DeleteOAuth2TokenParams) WithClientID(clientID string) *DeleteOAuth2TokenParams { + o.SetClientID(clientID) + return o +} + +// SetClientID adds the clientId to the delete o auth2 token params +func (o *DeleteOAuth2TokenParams) SetClientID(clientID string) { + o.ClientID = clientID +} + +// WriteToRequest writes these params to a swagger request +func (o *DeleteOAuth2TokenParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + // query param client_id + qrClientID := o.ClientID + qClientID := qrClientID + if qClientID != "" { + if err := r.SetQueryParam("client_id", qClientID); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/internal/httpclient/client/admin/delete_o_auth2_token_responses.go b/internal/httpclient/client/admin/delete_o_auth2_token_responses.go new file mode 100644 index 00000000000..b337ce6bf37 --- /dev/null +++ b/internal/httpclient/client/admin/delete_o_auth2_token_responses.go @@ -0,0 +1,136 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package admin + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/ory/hydra/internal/httpclient/models" +) + +// DeleteOAuth2TokenReader is a Reader for the DeleteOAuth2Token structure. +type DeleteOAuth2TokenReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *DeleteOAuth2TokenReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 204: + result := NewDeleteOAuth2TokenNoContent() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 401: + result := NewDeleteOAuth2TokenUnauthorized() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 500: + result := NewDeleteOAuth2TokenInternalServerError() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + + default: + return nil, runtime.NewAPIError("unknown error", response, response.Code()) + } +} + +// NewDeleteOAuth2TokenNoContent creates a DeleteOAuth2TokenNoContent with default headers values +func NewDeleteOAuth2TokenNoContent() *DeleteOAuth2TokenNoContent { + return &DeleteOAuth2TokenNoContent{} +} + +/*DeleteOAuth2TokenNoContent handles this case with default header values. + +Empty responses are sent when, for example, resources are deleted. The HTTP status code for empty responses is +typically 201. +*/ +type DeleteOAuth2TokenNoContent struct { +} + +func (o *DeleteOAuth2TokenNoContent) Error() string { + return fmt.Sprintf("[DELETE /oauth2/tokens][%d] deleteOAuth2TokenNoContent ", 204) +} + +func (o *DeleteOAuth2TokenNoContent) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewDeleteOAuth2TokenUnauthorized creates a DeleteOAuth2TokenUnauthorized with default headers values +func NewDeleteOAuth2TokenUnauthorized() *DeleteOAuth2TokenUnauthorized { + return &DeleteOAuth2TokenUnauthorized{} +} + +/*DeleteOAuth2TokenUnauthorized handles this case with default header values. + +genericError +*/ +type DeleteOAuth2TokenUnauthorized struct { + Payload *models.GenericError +} + +func (o *DeleteOAuth2TokenUnauthorized) Error() string { + return fmt.Sprintf("[DELETE /oauth2/tokens][%d] deleteOAuth2TokenUnauthorized %+v", 401, o.Payload) +} + +func (o *DeleteOAuth2TokenUnauthorized) GetPayload() *models.GenericError { + return o.Payload +} + +func (o *DeleteOAuth2TokenUnauthorized) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.GenericError) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewDeleteOAuth2TokenInternalServerError creates a DeleteOAuth2TokenInternalServerError with default headers values +func NewDeleteOAuth2TokenInternalServerError() *DeleteOAuth2TokenInternalServerError { + return &DeleteOAuth2TokenInternalServerError{} +} + +/*DeleteOAuth2TokenInternalServerError handles this case with default header values. + +genericError +*/ +type DeleteOAuth2TokenInternalServerError struct { + Payload *models.GenericError +} + +func (o *DeleteOAuth2TokenInternalServerError) Error() string { + return fmt.Sprintf("[DELETE /oauth2/tokens][%d] deleteOAuth2TokenInternalServerError %+v", 500, o.Payload) +} + +func (o *DeleteOAuth2TokenInternalServerError) GetPayload() *models.GenericError { + return o.Payload +} + +func (o *DeleteOAuth2TokenInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.GenericError) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/oauth2/doc.go b/oauth2/doc.go index 11b5f37afa2..5d1d09e4ffc 100644 --- a/oauth2/doc.go +++ b/oauth2/doc.go @@ -145,6 +145,13 @@ type WellKnown struct { EndSessionEndpoint string `json:"end_session_endpoint"` } +// swagger:parameters deleteOAuth2Token +type swaggerDeleteOAuth2Token struct { + //required: true + // in: query + ClientID string `json:"client_id"` +} + // swagger:model flushInactiveOAuth2TokensRequest type FlushInactiveOAuth2TokensRequest struct { // NotAfter sets after which point tokens should not be flushed. This is useful when you want to keep a history diff --git a/oauth2/fosite_store_helpers.go b/oauth2/fosite_store_helpers.go index 00892b22897..68de91e2524 100644 --- a/oauth2/fosite_store_helpers.go +++ b/oauth2/fosite_store_helpers.go @@ -171,6 +171,7 @@ func TestHelperRunner(t *testing.T, store InternalRegistry, k string) { t.Run(fmt.Sprintf("case=testHelperFlushTokens/db=%s", k), testHelperFlushTokens(store, time.Hour)) t.Run(fmt.Sprintf("case=testFositeStoreSetClientAssertionJWT/db=%s", k), testFositeStoreSetClientAssertionJWT(store)) t.Run(fmt.Sprintf("case=testFositeStoreClientAssertionJWTValid/db=%s", k), testFositeStoreClientAssertionJWTValid(store)) + t.Run(fmt.Sprintf("case=testHelperDeleteAccessTokens/db=%s", k), testHelperDeleteAccessTokens(store)) } func testHelperUniqueConstraints(m InternalRegistry, storageType string) func(t *testing.T) { @@ -368,6 +369,25 @@ func testHelperCreateGetDeleteAccessTokenSession(x InternalRegistry) func(t *tes } } +func testHelperDeleteAccessTokens(x InternalRegistry) func(t *testing.T) { + return func(t *testing.T) { + m := x.OAuth2Storage() + ctx := context.Background() + + err := m.CreateAccessTokenSession(ctx, "4321", &defaultRequest) + require.NoError(t, err) + + _, err = m.GetAccessTokenSession(ctx, "4321", &Session{}) + require.NoError(t, err) + + err = m.DeleteAccessTokens(ctx, defaultRequest.Client.GetID()) + require.NoError(t, err) + + _, err = m.GetAccessTokenSession(ctx, "4321", &Session{}) + assert.Error(t, err) + } +} + func testHelperCreateGetDeletePKCERequestSession(x InternalRegistry) func(t *testing.T) { return func(t *testing.T) { m := x.OAuth2Storage() diff --git a/oauth2/fosite_store_memory.go b/oauth2/fosite_store_memory.go index 58f2a7d8f72..471df483d22 100644 --- a/oauth2/fosite_store_memory.go +++ b/oauth2/fosite_store_memory.go @@ -410,3 +410,18 @@ func (s *FositeMemoryStore) DeletePKCERequestSession(_ context.Context, code str s.Unlock() return nil } + +func (s *FositeMemoryStore) DeleteAccessTokens(ctx context.Context, clientID string) error { + s.Lock() + defer s.Unlock() + + for sig, token := range s.AccessTokens { + if token.GetClient().GetID() == clientID { + if err := s.deleteAccessTokenSession(ctx, sig); err != nil { + return err + } + } + } + + return nil +} diff --git a/oauth2/fosite_store_sql.go b/oauth2/fosite_store_sql.go index 8cfc95c68c5..8aa72cc3126 100644 --- a/oauth2/fosite_store_sql.go +++ b/oauth2/fosite_store_sql.go @@ -519,3 +519,13 @@ func (s *FositeSQLStore) Rollback(ctx context.Context) error { return tx.Rollback() } } + +func (s *FositeSQLStore) DeleteAccessTokens(ctx context.Context, clientID string) error { + if _, err := s.DB.ExecContext(ctx, s.DB.Rebind(fmt.Sprintf("DELETE FROM hydra_oauth2_%s WHERE client_id=?", sqlTableAccess)), clientID); err == sql.ErrNoRows { + return errors.Wrap(fosite.ErrNotFound, "") + } else if err != nil { + return sqlcon.HandleError(err) + } + + return nil +} diff --git a/oauth2/handler.go b/oauth2/handler.go index 14e6e9e8510..2cd45e9ea8c 100644 --- a/oauth2/handler.go +++ b/oauth2/handler.go @@ -60,9 +60,10 @@ const ( JWKPath = "/.well-known/jwks.json" // IntrospectPath points to the OAuth2 introspection endpoint. - IntrospectPath = "/oauth2/introspect" - RevocationPath = "/oauth2/revoke" - FlushPath = "/oauth2/flush" + IntrospectPath = "/oauth2/introspect" + RevocationPath = "/oauth2/revoke" + FlushPath = "/oauth2/flush" + DeleteTokensPath = "/oauth2/tokens" // #nosec G101 ) type Handler struct { @@ -104,6 +105,7 @@ func (h *Handler) SetRoutes(admin *x.RouterAdmin, public *x.RouterPublic, corsMi admin.POST(IntrospectPath, h.IntrospectHandler) admin.POST(FlushPath, h.FlushHandler) + admin.DELETE(DeleteTokensPath, h.DeleteHandler) } // swagger:route GET /oauth2/sessions/logout public disconnectUser @@ -727,6 +729,37 @@ func (h *Handler) forwardError(w http.ResponseWriter, r *http.Request, err error http.Redirect(w, r, urlx.CopyWithQuery(h.c.ErrorURL(), query).String(), http.StatusFound) } +// swagger:route DELETE /oauth2/tokens admin deleteOAuth2Token +// +// Delete OAuth2 Access Tokens from a client +// +// This endpoint deletes OAuth2 access tokens issued for a client from the database +// +// Consumes: +// - application/json +// +// Schemes: http, https +// +// Responses: +// 204: emptyResponse +// 401: genericError +// 500: genericError +func (h *Handler) DeleteHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + client := r.URL.Query().Get("client_id") + + if client == "" { + h.r.Writer().WriteError(w, r, errors.WithStack(fosite.ErrInvalidRequest.WithHint(`Query parameter "client" is not defined but it should have been.`))) + return + } + + if err := h.r.OAuth2Storage().DeleteAccessTokens(r.Context(), client); err != nil { + h.r.Writer().WriteError(w, r, err) + return + } + + w.WriteHeader(http.StatusNoContent) +} + // This function will not be called, OPTIONS request will be handled by cors // this is just a placeholder. func (h *Handler) handleOptions(w http.ResponseWriter, r *http.Request) {} diff --git a/oauth2/handler_test.go b/oauth2/handler_test.go index ebf4ca3ba6f..bccf2d041a5 100644 --- a/oauth2/handler_test.go +++ b/oauth2/handler_test.go @@ -91,6 +91,45 @@ var flushRequests = []*fosite.Request{ }, } +func TestHandlerDeleteHandler(t *testing.T) { + conf := internal.NewConfigurationWithDefaults() + viper.Set(configuration.ViperKeyIssuerURL, "http://hydra.localhost") + reg := internal.NewRegistryMemory(conf) + + cm := reg.ClientManager() + store := reg.OAuth2Storage() + + h := oauth2.NewHandler(reg, conf) + + deleteRequest := &fosite.Request{ + ID: "del-1", + RequestedAt: time.Now().Round(time.Second), + Client: &client.Client{ID: "foobar"}, + RequestedScope: fosite.Arguments{"fa", "ba"}, + GrantedScope: fosite.Arguments{"fa", "ba"}, + Form: url.Values{"foo": []string{"bar", "baz"}}, + Session: &oauth2.Session{DefaultSession: &openid.DefaultSession{Subject: "bar"}}, + } + require.NoError(t, store.CreateAccessTokenSession(nil, deleteRequest.ID, deleteRequest)) + _ = cm.CreateClient(nil, deleteRequest.Client.(*client.Client)) + + r := x.NewRouterAdmin() + h.SetRoutes(r, r.RouterPublic(), func(h http.Handler) http.Handler { + return h + }) + ts := httptest.NewServer(r) + defer ts.Close() + + c := hydra.NewHTTPClientWithConfig(nil, &hydra.TransportConfig{Schemes: []string{"http"}, Host: urlx.ParseOrPanic(ts.URL).Host}) + _, err := c.Admin.DeleteOAuth2Token(admin.NewDeleteOAuth2TokenParams().WithClientID("foobar")) + require.NoError(t, err) + + ds := new(oauth2.Session) + ctx := context.Background() + _, err = store.GetAccessTokenSession(ctx, "del-1", ds) + require.Error(t, err, "not_found") +} + func TestHandlerFlushHandler(t *testing.T) { conf := internal.NewConfigurationWithDefaults() viper.Set(configuration.ViperKeyScopeStrategy, "DEPRECATED_HIERARCHICAL_SCOPE_STRATEGY") diff --git a/x/fosite_storer.go b/x/fosite_storer.go index b587e9a28d1..b41c0b13767 100644 --- a/x/fosite_storer.go +++ b/x/fosite_storer.go @@ -41,4 +41,6 @@ type FositeStorer interface { RevokeAccessToken(ctx context.Context, requestID string) error FlushInactiveAccessTokens(ctx context.Context, notAfter time.Time) error + + DeleteAccessTokens(ctx context.Context, clientID string) error }