PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.5
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.5
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
← All changes | src/ai/authorization/application/token-manager.php +47 -16 28.028.5 View file →
@@ -114,10 +114,15 @@
114 114
115 115 /**
116 116 * Invalidates the access token.
117 117 *
118 - * @param string $user_id The user ID.
118 + * The locally stored JWTs are always cleared, even when the remote invalidation fails — the
119 + * remote exception still propagates to the caller, but no credentials are left behind.
119 120 *
121 + * @param int $user_id The user ID.
122 + *
123 + * @return void
124 + *
120 125 * @throws Bad_Request_Exception Bad_Request_Exception.
121 126 * @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
122 127 * @throws Not_Found_Exception Not_Found_Exception.
123 128 * @throws Payment_Required_Exception Payment_Required_Exception.
@@ -124,11 +129,10 @@
124 129 * @throws Request_Timeout_Exception Request_Timeout_Exception.
125 130 * @throws Service_Unavailable_Exception Service_Unavailable_Exception.
126 131 * @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
127 132 * @throws RuntimeException Unable to retrieve the access token.
128 - * @return void
129 133 */
130 - public function token_invalidate( string $user_id ): void {
134 + public function token_invalidate( int $user_id ): void {
131 135 try {
132 136 $access_jwt = $this->access_token_repository->get_token( $user_id );
133 137 } catch ( RuntimeException $e ) {
134 138 $access_jwt = '';
@@ -133,43 +137,68 @@
133 137 } catch ( RuntimeException $e ) {
134 138 $access_jwt = '';
135 139 }
136 140
137 - $request_body = [
138 - 'user_id' => (string) $user_id,
139 - ];
140 141 $request_headers = [
141 142 'Authorization' => "Bearer $access_jwt",
142 143 ];
143 144
144 145 try {
146 + // The endpoint takes no request body; the user is identified by the access token.
145 147 $this->request_handler->handle(
146 148 new Request(
147 149 '/token/invalidate',
148 - $request_body,
150 + [],
149 151 $request_headers,
150 152 ),
151 153 );
152 154 } catch ( Unauthorized_Exception | Forbidden_Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Reason: Ignored on purpose.
153 155 // If the credentials in our request were already invalid, our job is done and we continue to remove the tokens client-side.
156 + } finally {
157 + // Always clear the local tokens, even when the remote invalidation fails with an exception
158 + // that propagates: leaving credentials behind would contradict the intent of invalidating.
159 + $this->clear_tokens( $user_id );
154 160 }
155 -
156 - $this->clear_tokens( $user_id );
157 161 }
158 162
159 163 /**
160 164 * Clears the user meta tokens for a specific user.
161 165 *
162 - * @param string $user_id The user id to delete this for.
166 + * @param int $user_id The user id to delete this for.
163 167 *
164 168 * @return void
165 169 */
166 - public function clear_tokens( string $user_id ): void {
170 + public function clear_tokens( int $user_id ): void {
167 171 $this->access_token_repository->delete_token( $user_id );
168 172 $this->refresh_token_repository->delete_token( $user_id );
169 173 }
170 174
171 175 /**
176 + * Checks whether any JWT (access or refresh) is stored locally for the user.
177 + *
178 + * @param int $user_id The user ID.
179 + *
180 + * @return bool Whether a locally stored JWT exists.
181 + */
182 + public function has_local_tokens( int $user_id ): bool {
183 + try {
184 + $this->access_token_repository->get_token( $user_id );
185 +
186 + return true;
187 + } catch ( RuntimeException $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- Reason: Ignored on purpose.
188 + // No access token; fall through to the refresh token check.
189 + }
190 +
191 + try {
192 + $this->refresh_token_repository->get_token( $user_id );
193 +
194 + return true;
195 + } catch ( RuntimeException $e ) {
196 + return false;
197 + }
198 + }
199 +
200 + /**
172 201 * Requests a new set of JWT tokens.
173 202 *
174 203 * Requests a new JWT access and refresh token for a user from the Yoast AI Service and stores it in the database
175 204 * under usermeta. The storing of the token happens in a HTTP callback that is triggered by this request.
@@ -175,8 +204,10 @@
175 204 * under usermeta. The storing of the token happens in a HTTP callback that is triggered by this request.
176 205 *
177 206 * @param WP_User $user The WP user.
178 207 *
208 + * @return void
209 + *
179 210 * @throws Bad_Request_Exception Bad_Request_Exception.
180 211 * @throws Forbidden_Exception Forbidden_Exception.
181 212 * @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
182 213 * @throws Not_Found_Exception Not_Found_Exception.
@@ -184,9 +215,8 @@
184 215 * @throws Request_Timeout_Exception Request_Timeout_Exception.
185 216 * @throws Service_Unavailable_Exception Service_Unavailable_Exception.
186 217 * @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
187 218 * @throws Unauthorized_Exception Unauthorized_Exception.
188 - * @return void
189 219 */
190 220 public function token_request( WP_User $user ): void {
191 221 // Generate a code verifier and store it in the database.
192 222 $code_verifier = $this->code_verifier->generate( $user->user_email );
@@ -220,8 +250,10 @@
220 250 * usermeta. The storing of the token happens in a HTTP callback that is triggered by this request.
221 251 *
222 252 * @param WP_User $user The WP user.
223 253 *
254 + * @return void
255 + *
224 256 * @throws Bad_Request_Exception Bad_Request_Exception.
225 257 * @throws Forbidden_Exception Forbidden_Exception.
226 258 * @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
227 259 * @throws Not_Found_Exception Not_Found_Exception.
@@ -230,9 +262,8 @@
230 262 * @throws Service_Unavailable_Exception Service_Unavailable_Exception.
231 263 * @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
232 264 * @throws Unauthorized_Exception Unauthorized_Exception.
233 265 * @throws RuntimeException Unable to retrieve the refresh token.
234 - * @return void
235 266 */
236 267 public function token_refresh( WP_User $user ): void {
237 268 $refresh_jwt = $this->refresh_token_repository->get_token( $user->ID );
238 269
@@ -286,8 +317,10 @@
286 317 * Retrieves the access token.
287 318 *
288 319 * @param WP_User $user The WP user.
289 320 *
321 + * @return string The access token.
322 + *
290 323 * @throws Bad_Request_Exception Bad_Request_Exception.
291 324 * @throws Forbidden_Exception Forbidden_Exception.
292 325 * @throws Internal_Server_Error_Exception Internal_Server_Error_Exception.
293 326 * @throws Not_Found_Exception Not_Found_Exception.
@@ -296,15 +329,13 @@
296 329 * @throws Service_Unavailable_Exception Service_Unavailable_Exception.
297 330 * @throws Too_Many_Requests_Exception Too_Many_Requests_Exception.
298 331 * @throws Unauthorized_Exception Unauthorized_Exception.
299 332 * @throws RuntimeException Unable to retrieve the access or refresh token.
300 - * @return string The access token.
301 333 */
302 334 public function get_or_request_access_token( WP_User $user ): string {
303 335 // If the site URL has changed since callback URLs were registered, delete stale tokens.
304 336 if ( $this->have_callback_urls_changed( $user ) ) {
305 - $this->user_helper->delete_meta( $user->ID, '_yoast_wpseo_ai_generator_access_jwt' );
306 - $this->user_helper->delete_meta( $user->ID, '_yoast_wpseo_ai_generator_refresh_jwt' );
337 + $this->clear_tokens( $user->ID );
307 338 }
308 339
309 340 $access_jwt = $this->user_helper->get_meta( $user->ID, '_yoast_wpseo_ai_generator_access_jwt', true );
310 341 if ( ! \is_string( $access_jwt ) || $access_jwt === '' ) {