|
7 | 7 | use RuntimeException; |
8 | 8 | use WP_User; |
9 | 9 | use Yoast\WP\SEO\AI\Authentication\Application\AI_Request_Sender_Factory; |
| 10 | +use Yoast\WP\SEO\AI\Authorization\Application\Token_Manager; |
10 | 11 | use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Bad_Request_Exception; |
11 | 12 | use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Consent_Required_Exception; |
12 | 13 | use Yoast\WP\SEO\AI\HTTP_Request\Domain\Exceptions\Forbidden_Exception; |
@@ -43,18 +44,28 @@ class Consent_Handler implements Consent_Handler_Interface { |
43 | 44 | */ |
44 | 45 | private $ai_request_sender_factory; |
45 | 46 |
|
| 47 | + /** |
| 48 | + * The token manager, used to invalidate leftover legacy JWTs when consent is revoked. |
| 49 | + * |
| 50 | + * @var Token_Manager |
| 51 | + */ |
| 52 | + private $token_manager; |
| 53 | + |
46 | 54 | /** |
47 | 55 | * Class constructor. |
48 | 56 | * |
49 | 57 | * @param User_Helper $user_helper The user helper. |
50 | 58 | * @param AI_Request_Sender_Factory $ai_request_sender_factory The AI request sender factory. |
| 59 | + * @param Token_Manager $token_manager The token manager. |
51 | 60 | */ |
52 | 61 | public function __construct( |
53 | 62 | User_Helper $user_helper, |
54 | | - AI_Request_Sender_Factory $ai_request_sender_factory |
| 63 | + AI_Request_Sender_Factory $ai_request_sender_factory, |
| 64 | + Token_Manager $token_manager |
55 | 65 | ) { |
56 | 66 | $this->user_helper = $user_helper; |
57 | 67 | $this->ai_request_sender_factory = $ai_request_sender_factory; |
| 68 | + $this->token_manager = $token_manager; |
58 | 69 | } |
59 | 70 |
|
60 | 71 | // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- PHPCS doesn't take into account exceptions thrown in called methods. |
@@ -99,8 +110,11 @@ public function grant_consent( int $user_id ) { |
99 | 110 | * Revokes the user's consent on the Yoast AI service and clears the local user meta. |
100 | 111 | * |
101 | 112 | * Security-first: the local meta is always cleared before the remote call, so consent is |
102 | | - * revoked locally even if the remote DELETE fails. Any HTTP-layer exception is propagated |
103 | | - * and its management is deferred to the caller. |
| 113 | + * revoked locally even if the remote `DELETE /user/consent` fails. Any locally stored legacy |
| 114 | + * JWTs are then invalidated regardless of the remote outcome — credentials must not outlive |
| 115 | + * consent. The invalidation runs after the DELETE on purpose: the legacy Token path may mint |
| 116 | + * a fresh JWT to authenticate the DELETE, and invalidating afterwards catches that token too. |
| 117 | + * Any HTTP-layer exception is propagated and its management is deferred to the caller. |
104 | 118 | * |
105 | 119 | * @param int $user_id The user ID. |
106 | 120 | * |
@@ -129,7 +143,16 @@ public function revoke_consent( int $user_id ) { |
129 | 143 | // Local consent is always revoked regardless of remote failures. |
130 | 144 | $this->user_helper->delete_meta( $user_id, '_yoast_wpseo_ai_consent' ); |
131 | 145 |
|
132 | | - $this->ai_request_sender_factory->create( $user )->revoke_consent( $user ); |
| 146 | + try { |
| 147 | + $this->ai_request_sender_factory->create( $user )->revoke_consent( $user ); |
| 148 | + } finally { |
| 149 | + // Invalidate the legacy JWTs — including ones minted to authenticate the DELETE above — |
| 150 | + // so credentials never outlive consent. Skipped when no local JWTs exist (the OAuth path |
| 151 | + // without a leftover pre-OAuth grant). |
| 152 | + if ( $this->token_manager->has_local_tokens( $user_id ) ) { |
| 153 | + $this->token_manager->token_invalidate( $user_id ); |
| 154 | + } |
| 155 | + } |
133 | 156 | } |
134 | 157 |
|
135 | 158 | // phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber |
|
0 commit comments