From 4b48b43d478052991293fa01f12a6377921293a1 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 7 Apr 2026 12:09:55 +0800 Subject: [PATCH 1/4] Add `revoke_token` method --- src/class-convertkit-api-v4.php | 57 +++++++++++++++++++++++++++++++++ tests/Integration/APITest.php | 25 +++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/class-convertkit-api-v4.php b/src/class-convertkit-api-v4.php index ad3762a..ca4998e 100644 --- a/src/class-convertkit-api-v4.php +++ b/src/class-convertkit-api-v4.php @@ -89,6 +89,7 @@ class ConvertKit_API_V4 { */ protected $api_endpoints_oauth = array( 'token', + 'revoke', ); /** @@ -464,6 +465,62 @@ public function refresh_token() { } + /** + * Revokes the current access token. + * + * @since 2.1.4 + */ + public function revoke_token() { + + $result = $this->post( + 'revoke', + array( + 'client_id' => $this->client_id, + 'access_token' => $this->access_token, + ) + ); + + // If an error occured, log and return it now. + if ( is_wp_error( $result ) ) { + $this->log( 'API: Error: ' . $result->get_error_message() ); + + /** + * Perform any actions when revoking an access token fails. + * + * @since 2.1.4 + * + * @param WP_Error $result Error from API. + * @param string $client_id OAuth Client ID. + */ + do_action( 'convertkit_api_revoke_token_error', $result, $this->client_id ); + + return $result; + } + + // Store existing access and refresh tokens. + $previous_access_token = $this->access_token; + $previous_refresh_token = $this->refresh_token; + + // Remove access and refresh tokens from this class. + $this->access_token = ''; + $this->refresh_token = ''; + + /** + * Perform any actions when the access token is revoked, such as deleting them from the database. + * + * @since 2.1.4 + * + * @param string $client_id OAuth Client ID. + * @param string $previous_access_token Existing Access Token. + * @param string $previous_refresh_token Existing Refresh Token. + */ + do_action( 'convertkit_api_revoke_token', $this->client_id, $previous_access_token, $previous_refresh_token ); + + // Return. + return $result; + + } + /** * Exchanges the given API Key for an Access Token. * diff --git a/tests/Integration/APITest.php b/tests/Integration/APITest.php index ff9e405..2f1e351 100644 --- a/tests/Integration/APITest.php +++ b/tests/Integration/APITest.php @@ -528,6 +528,31 @@ public function testRefreshTokenWithInvalidToken() $this->assertEquals($result->get_error_code(), 'convertkit_api_error'); } + /** + * Test that revoke_token() returns the expected data. + * + * @since 2.1.4 + * + * @return void + */ + public function testRevokeToken() + { + // Add mock handler for this API request, as this results in the + // access token being revoked, which would result in + // other tests breaking due to changed tokens. + $this->mockResponses( + httpCode: 200, + httpMessage: 'OK', + body: wp_json_encode( + array() + ) + ); + + $result = $this->api->revoke_token(); + $this->assertNotInstanceOf(\WP_Error::class, $result); + $this->assertIsArray($result); + } + /** * Test that supplying no API credentials to the API class returns a WP_Error. * From e44a5f72339d83db51a08140c81f1c40a52e09de Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 7 Apr 2026 13:32:51 +0800 Subject: [PATCH 2/4] Tests: Fetch and revoke an actual token --- tests/Integration/APITest.php | 43 +++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/tests/Integration/APITest.php b/tests/Integration/APITest.php index 2f1e351..c1a6a3e 100644 --- a/tests/Integration/APITest.php +++ b/tests/Integration/APITest.php @@ -529,28 +529,41 @@ public function testRefreshTokenWithInvalidToken() } /** - * Test that revoke_token() returns the expected data. + * Test that the access token and refresh token are revoked when revoke_token() is called. * * @since 2.1.4 - * - * @return void */ public function testRevokeToken() { - // Add mock handler for this API request, as this results in the - // access token being revoked, which would result in - // other tests breaking due to changed tokens. - $this->mockResponses( - httpCode: 200, - httpMessage: 'OK', - body: wp_json_encode( - array() - ) + // Initialize the API without an access token or refresh token. + $api = new \ConvertKit_API_V4( + $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + $_ENV['CONVERTKIT_OAUTH_REDIRECT_URI'] ); - $result = $this->api->revoke_token(); - $this->assertNotInstanceOf(\WP_Error::class, $result); - $this->assertIsArray($result); + // Generate an access token by API key and secret. + $result = $api->get_access_token_by_api_key_and_secret( + $_ENV['CONVERTKIT_API_KEY'], + $_ENV['CONVERTKIT_API_SECRET'], + wp_generate_password( 10, false ) // Random tenant name to produce a token for this request only. + ); + + // Initialize the API with the access token and refresh token. + $api = new \ConvertKit_API_V4( + $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + $_ENV['CONVERTKIT_OAUTH_REDIRECT_URI'], + $result['oauth']['access_token'], + $result['oauth']['refresh_token'] + ); + + // Confirm the token works when making an authenticated request. + $this->assertNotInstanceOf( 'WP_Error', $api->get_account() ); + + // Revoke the access token. + $api->revoke_token(); + + // Confirm the token no longer works when making an authenticated request. + $this->assertInstanceOf( 'WP_Error', $api->get_account() ); } /** From d5d829e4eab2e2d1d622dc903b8995f0d2b38d5f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 09:47:07 +0800 Subject: [PATCH 3/4] Revoke both access and refresh token, using correct `token` parameter --- src/class-convertkit-api-v4.php | 41 +++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/class-convertkit-api-v4.php b/src/class-convertkit-api-v4.php index ca4998e..fcc26b9 100644 --- a/src/class-convertkit-api-v4.php +++ b/src/class-convertkit-api-v4.php @@ -466,17 +466,18 @@ public function refresh_token() { } /** - * Revokes the current access token. + * Revokes the current access and refresh tokens. * * @since 2.1.4 */ - public function revoke_token() { + public function revoke_tokens() { + // Revoke the access token. $result = $this->post( 'revoke', array( - 'client_id' => $this->client_id, - 'access_token' => $this->access_token, + 'client_id' => $this->client_id, + 'token' => $this->access_token, ) ); @@ -492,7 +493,33 @@ public function revoke_token() { * @param WP_Error $result Error from API. * @param string $client_id OAuth Client ID. */ - do_action( 'convertkit_api_revoke_token_error', $result, $this->client_id ); + do_action( 'convertkit_api_revoke_tokens_access_token_error', $result, $this->client_id ); + + return $result; + } + + // Revoke the refresh token. + $result = $this->post( + 'revoke', + array( + 'client_id' => $this->client_id, + 'token' => $this->refresh_token, + ) + ); + + // If an error occured, log and return it now. + if ( is_wp_error( $result ) ) { + $this->log( 'API: Error: ' . $result->get_error_message() ); + + /** + * Perform any actions when revoking a refresh token fails. + * + * @since 2.1.4 + * + * @param WP_Error $result Error from API. + * @param string $client_id OAuth Client ID. + */ + do_action( 'convertkit_api_revoke_tokens_refresh_token_error', $result, $this->client_id ); return $result; } @@ -506,7 +533,7 @@ public function revoke_token() { $this->refresh_token = ''; /** - * Perform any actions when the access token is revoked, such as deleting them from the database. + * Perform any actions when the tokens are revoked, such as deleting them from the database. * * @since 2.1.4 * @@ -514,7 +541,7 @@ public function revoke_token() { * @param string $previous_access_token Existing Access Token. * @param string $previous_refresh_token Existing Refresh Token. */ - do_action( 'convertkit_api_revoke_token', $this->client_id, $previous_access_token, $previous_refresh_token ); + do_action( 'convertkit_api_revoke_tokens', $this->client_id, $previous_access_token, $previous_refresh_token ); // Return. return $result; From a1d480017342b625ce3ae02eaf64e3a2121369ab Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 8 Apr 2026 09:47:18 +0800 Subject: [PATCH 4/4] Improve test coverage to check both access and refresh token revoked --- tests/Integration/APITest.php | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/Integration/APITest.php b/tests/Integration/APITest.php index c1a6a3e..ad34182 100644 --- a/tests/Integration/APITest.php +++ b/tests/Integration/APITest.php @@ -529,11 +529,11 @@ public function testRefreshTokenWithInvalidToken() } /** - * Test that the access token and refresh token are revoked when revoke_token() is called. + * Test that the access token and refresh token are revoked when revoke_tokens() is called. * * @since 2.1.4 */ - public function testRevokeToken() + public function testRevokeTokens() { // Initialize the API without an access token or refresh token. $api = new \ConvertKit_API_V4( @@ -559,11 +559,24 @@ public function testRevokeToken() // Confirm the token works when making an authenticated request. $this->assertNotInstanceOf( 'WP_Error', $api->get_account() ); - // Revoke the access token. - $api->revoke_token(); + // Revoke the access and refresh tokens. + $api->revoke_tokens(); - // Confirm the token no longer works when making an authenticated request. + // Initialize the API with the (now revoked) access token and refresh token. + // revoke_tokens() will have removed the access token and refresh token from the API class, so we need to provide them again + // to test they're revoked. + $api = new \ConvertKit_API_V4( + $_ENV['CONVERTKIT_OAUTH_CLIENT_ID'], + $_ENV['CONVERTKIT_OAUTH_REDIRECT_URI'], + $result['oauth']['access_token'], + $result['oauth']['refresh_token'] + ); + + // Confirm attempting to use the revoked access token no longer works. $this->assertInstanceOf( 'WP_Error', $api->get_account() ); + + // Confirm attempting to use the revoked refresh token no longer works. + $this->assertInstanceOf( 'WP_Error', $api->refresh_token() ); } /**