| @@ -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 === '' ) { |