PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.8.4
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.8.4
3.5.9 3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / labs / oauth.php
ai-engine / labs Last commit date
mcp.conf 1 year ago mcp.js 1 year ago mcp.md 1 year ago mcp.php 1 year ago mcp_core.php 1 year ago mcp_rest.php 1 year ago oauth.php 1 year ago realtime.php 1 year ago
oauth.php
387 lines
1 <?php
2
3 class Meow_MWAI_Labs_OAuth {
4 private $core = null;
5 private $namespace = 'mcp/oauth';
6 private $codes_option = 'mwai_oauth_codes';
7 private $tokens_option = 'mwai_oauth_tokens';
8 private $code_lifetime = 600; // 10 minutes
9 private $token_lifetime = 3600; // 1 hour
10 private $logging = false;
11
12 public function __construct( $core, $logging = false ) {
13 $this->core = $core;
14 $this->logging = $logging;
15
16 add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
17 add_action( 'init', [ $this, 'handle_well_known' ] );
18 }
19
20 public function rest_api_init() {
21 // Authorization endpoint
22 register_rest_route( $this->namespace, '/authorize', [
23 'methods' => 'GET',
24 'callback' => [ $this, 'handle_authorize' ],
25 'permission_callback' => '__return_true',
26 ] );
27
28 // Token endpoint
29 register_rest_route( $this->namespace, '/token', [
30 'methods' => 'POST',
31 'callback' => [ $this, 'handle_token' ],
32 'permission_callback' => '__return_true',
33 ] );
34 }
35
36 // Handle .well-known/oauth-authorization-server
37 public function handle_well_known() {
38 if ( $_SERVER['REQUEST_URI'] === '/.well-known/oauth-authorization-server' ) {
39 if ( $this->logging ) {
40 error_log( '[OAuth] 🌐 Discovery endpoint requested.' );
41 }
42 header( 'Content-Type: application/json' );
43 $base_url = get_site_url();
44 $discovery = [
45 'issuer' => $base_url,
46 'authorization_endpoint' => $base_url . '/wp-json/mcp/oauth/authorize',
47 'token_endpoint' => $base_url . '/wp-json/mcp/oauth/token',
48 'scopes_supported' => [ 'mcp' ],
49 'response_types_supported' => [ 'code' ],
50 'grant_types_supported' => [ 'authorization_code' ],
51 'token_endpoint_auth_methods_supported' => [ 'none' ],
52 'code_challenge_methods_supported' => [ 'S256' ]
53 ];
54 echo json_encode( $discovery, JSON_PRETTY_PRINT );
55 exit;
56 }
57 }
58
59 // Authorization endpoint
60 public function handle_authorize( $request ) {
61 if ( $this->logging ) {
62 error_log( '[OAuth] 🔐 Authorize: ' . $request->get_param( 'client_id' ) );
63 }
64
65 $response_type = $request->get_param( 'response_type' );
66 $client_id = $request->get_param( 'client_id' );
67 $redirect_uri = $request->get_param( 'redirect_uri' );
68 $state = $request->get_param( 'state' );
69 $code_challenge = $request->get_param( 'code_challenge' );
70 $code_challenge_method = $request->get_param( 'code_challenge_method' );
71 $scope = $request->get_param( 'scope' );
72
73 // Validate request
74 if ( $response_type !== 'code' ) {
75 return new WP_Error( 'invalid_request', 'Invalid response_type' );
76 }
77
78 if ( empty( $client_id ) || empty( $redirect_uri ) || empty( $code_challenge ) ) {
79 return new WP_Error( 'invalid_request', 'Missing required parameters' );
80 }
81
82 if ( $code_challenge_method && $code_challenge_method !== 'S256' ) {
83 return new WP_Error( 'invalid_request', 'Only S256 code challenge method is supported' );
84 }
85
86 // Check if user is logged in
87 if ( ! is_user_logged_in() ) {
88 // Store OAuth params in session/transient
89 $session_key = 'oauth_' . wp_generate_password( 16, false );
90 set_transient( $session_key, [
91 'client_id' => $client_id,
92 'redirect_uri' => $redirect_uri,
93 'state' => $state,
94 'code_challenge' => $code_challenge,
95 'scope' => $scope ?: 'mcp'
96 ], 600 );
97
98 // Show login form
99 $this->show_login_form( $session_key );
100 exit;
101 }
102
103 // User is logged in, generate authorization code
104 $code = $this->generate_authorization_code(
105 get_current_user_id(),
106 $client_id,
107 $redirect_uri,
108 $code_challenge,
109 $scope ?: 'mcp'
110 );
111
112 // Redirect back with code
113 $redirect_params = [
114 'code' => $code,
115 'state' => $state
116 ];
117
118 $redirect_url = add_query_arg( $redirect_params, $redirect_uri );
119 wp_redirect( $redirect_url );
120 exit;
121 }
122
123 // Token endpoint
124 public function handle_token( $request ) {
125 if ( $this->logging ) {
126 $params = $request->get_params();
127 // Don't log sensitive data like code_verifier
128 $safe_params = $params;
129 if ( isset( $safe_params['code_verifier'] ) ) {
130 $safe_params['code_verifier'] = '[REDACTED]';
131 }
132 error_log( '[OAuth] 🎫 Token exchange for client: ' . $request->get_param( 'client_id' ) );
133 }
134
135 $grant_type = $request->get_param( 'grant_type' );
136 $code = $request->get_param( 'code' );
137 $client_id = $request->get_param( 'client_id' );
138 $redirect_uri = $request->get_param( 'redirect_uri' );
139 $code_verifier = $request->get_param( 'code_verifier' );
140
141 // Validate grant type
142 if ( $grant_type !== 'authorization_code' ) {
143 return new WP_Error( 'unsupported_grant_type', 'Only authorization_code grant type is supported', [ 'status' => 400 ] );
144 }
145
146 // Validate required parameters
147 if ( empty( $code ) || empty( $client_id ) || empty( $redirect_uri ) || empty( $code_verifier ) ) {
148 return new WP_Error( 'invalid_request', 'Missing required parameters', [ 'status' => 400 ] );
149 }
150
151 // Validate authorization code
152 $codes = get_option( $this->codes_option, [] );
153 if ( ! isset( $codes[ $code ] ) ) {
154 return new WP_Error( 'invalid_grant', 'Invalid authorization code', [ 'status' => 400 ] );
155 }
156
157 $code_data = $codes[ $code ];
158
159 // Check if code is expired
160 if ( time() > $code_data['expires'] ) {
161 unset( $codes[ $code ] );
162 update_option( $this->codes_option, $codes );
163 return new WP_Error( 'invalid_grant', 'Authorization code has expired', [ 'status' => 400 ] );
164 }
165
166 // Validate client_id and redirect_uri
167 if ( $code_data['client_id'] !== $client_id || $code_data['redirect_uri'] !== $redirect_uri ) {
168 return new WP_Error( 'invalid_grant', 'Invalid client_id or redirect_uri', [ 'status' => 400 ] );
169 }
170
171 // Verify PKCE
172 $verifier_hash = base64_encode( hash( 'sha256', $code_verifier, true ) );
173 $verifier_hash = rtrim( strtr( $verifier_hash, '+/', '-_' ), '=' ); // Base64 URL encoding
174
175 if ( $verifier_hash !== $code_data['code_challenge'] ) {
176 return new WP_Error( 'invalid_grant', 'Invalid code_verifier', [ 'status' => 400 ] );
177 }
178
179 // Code is valid, remove it (one-time use)
180 unset( $codes[ $code ] );
181 update_option( $this->codes_option, $codes );
182
183 // Generate access token
184 $access_token = $this->generate_access_token( $code_data['user_id'], $code_data['scope'] );
185
186 // Return token response
187 return [
188 'access_token' => $access_token,
189 'token_type' => 'Bearer',
190 'expires_in' => $this->token_lifetime,
191 'scope' => $code_data['scope']
192 ];
193 }
194
195 // Generate authorization code
196 private function generate_authorization_code( $user_id, $client_id, $redirect_uri, $code_challenge, $scope ) {
197 if ( $this->logging ) {
198 error_log( '[OAuth] �
199 Auth code generated for user ' . $user_id . '.' );
200 }
201
202 $code = wp_generate_password( 32, false );
203
204 $codes = get_option( $this->codes_option, [] );
205 $codes[ $code ] = [
206 'user_id' => $user_id,
207 'client_id' => $client_id,
208 'redirect_uri' => $redirect_uri,
209 'code_challenge' => $code_challenge,
210 'scope' => $scope,
211 'expires' => time() + $this->code_lifetime
212 ];
213
214 update_option( $this->codes_option, $codes );
215
216 return $code;
217 }
218
219 // Generate access token
220 private function generate_access_token( $user_id, $scope ) {
221 if ( $this->logging ) {
222 error_log( '[OAuth] �
223 Access token generated for user ' . $user_id . '.' );
224 }
225
226 $token = wp_generate_password( 40, false );
227
228 $tokens = get_option( $this->tokens_option, [] );
229 $tokens[ $token ] = [
230 'user_id' => $user_id,
231 'scope' => $scope,
232 'expires' => time() + $this->token_lifetime
233 ];
234
235 update_option( $this->tokens_option, $tokens );
236
237 return $token;
238 }
239
240 // Validate access token
241 public function validate_token( $token ) {
242 $tokens = get_option( $this->tokens_option, [] );
243
244 if ( ! isset( $tokens[ $token ] ) ) {
245 return false;
246 }
247
248 $token_data = $tokens[ $token ];
249
250 // Check if expired
251 if ( time() > $token_data['expires'] ) {
252 if ( $this->logging ) {
253 error_log( '[OAuth] ❌ Token validation failed: expired.' );
254 }
255 unset( $tokens[ $token ] );
256 update_option( $this->tokens_option, $tokens );
257 return false;
258 }
259
260 if ( $this->logging ) {
261 error_log( '[OAuth] �
262 Token valid for user ' . $token_data['user_id'] . '.' );
263 }
264
265 return $token_data;
266 }
267
268 // Show login form
269 private function show_login_form( $session_key ) {
270 $login_url = wp_login_url( add_query_arg( 'oauth_session', $session_key, $_SERVER['REQUEST_URI'] ) );
271 ?>
272 <!DOCTYPE html>
273 <html>
274 <head>
275 <title>Login Required</title>
276 <style>
277 body {
278 font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
279 display: flex;
280 justify-content: center;
281 align-items: center;
282 height: 100vh;
283 margin: 0;
284 background: #f5f5f5;
285 }
286 .login-container {
287 background: white;
288 padding: 40px;
289 border-radius: 8px;
290 box-shadow: 0 2px 4px rgba(0,0,0,0.1);
291 text-align: center;
292 max-width: 400px;
293 }
294 h2 {
295 margin-top: 0;
296 color: #333;
297 }
298 p {
299 color: #666;
300 margin-bottom: 30px;
301 }
302 .login-button {
303 display: inline-block;
304 background: #0073aa;
305 color: white;
306 padding: 12px 24px;
307 text-decoration: none;
308 border-radius: 4px;
309 font-weight: 500;
310 }
311 .login-button:hover {
312 background: #005a87;
313 }
314 </style>
315 </head>
316 <body>
317 <div class="login-container">
318 <h2>Authorization Required</h2>
319 <p>Please log in to authorize access to your MCP connector.</p>
320 <a href="<?php echo esc_url( $login_url ); ?>" class="login-button">Log In</a>
321 </div>
322 </body>
323 </html>
324 <?php
325 }
326
327 // Handle OAuth callback after login
328 public function handle_oauth_callback() {
329 if ( isset( $_GET['oauth_session'] ) && is_user_logged_in() ) {
330 if ( $this->logging ) {
331 error_log( '[OAuth] 🔄 Callback handling for session.' );
332 }
333 $session_key = sanitize_text_field( $_GET['oauth_session'] );
334 $oauth_params = get_transient( $session_key );
335
336 if ( $oauth_params ) {
337 delete_transient( $session_key );
338
339 // Generate authorization code
340 $code = $this->generate_authorization_code(
341 get_current_user_id(),
342 $oauth_params['client_id'],
343 $oauth_params['redirect_uri'],
344 $oauth_params['code_challenge'],
345 $oauth_params['scope']
346 );
347
348 // Redirect back with code
349 $redirect_params = [
350 'code' => $code,
351 'state' => $oauth_params['state']
352 ];
353
354 $redirect_url = add_query_arg( $redirect_params, $oauth_params['redirect_uri'] );
355 wp_redirect( $redirect_url );
356 exit;
357 }
358 }
359 }
360
361 // Clean up expired tokens and codes
362 public function cleanup_expired() {
363 if ( $this->logging ) {
364 error_log( '[OAuth] 🧹 Cleaning expired tokens.' );
365 }
366
367 $now = time();
368
369 // Clean codes
370 $codes = get_option( $this->codes_option, [] );
371 foreach ( $codes as $code => $data ) {
372 if ( $now > $data['expires'] ) {
373 unset( $codes[ $code ] );
374 }
375 }
376 update_option( $this->codes_option, $codes );
377
378 // Clean tokens
379 $tokens = get_option( $this->tokens_option, [] );
380 foreach ( $tokens as $token => $data ) {
381 if ( $now > $data['expires'] ) {
382 unset( $tokens[ $token ] );
383 }
384 }
385 update_option( $this->tokens_option, $tokens );
386 }
387 }