Changeset 3470796
- Timestamp:
- 02/27/2026 02:29:08 AM (11 days ago)
- Location:
- lightsyncpro/trunk
- Files:
-
- 2 added
- 1 edited
-
assets/screenshot-6.png (added)
-
assets/screenshot-7.png (added)
-
includes/oauth/class-openrouter-oauth.php (modified) (8 diffs)
Legend:
- Unmodified
- Added
- Removed
-
lightsyncpro/trunk/includes/oauth/class-openrouter-oauth.php
r3469756 r3470796 5 5 use LightSyncPro\Util\Crypto; 6 6 use LightSyncPro\Util\Logger; 7 8 if ( ! defined( 'ABSPATH' ) ) exit; 7 9 8 10 … … 12 14 * Handles broker-based OAuth PKCE flow for OpenRouter AI. 13 15 * 14 * KEY DIFFERENCE from Canva/Figma/Dropbox: 15 * - OpenRouter returns a persistent API key, NOT an expiring access token 16 * - No token refresh needed — the key is valid until user revokes it 17 * - API key is retrieved during OAuth and stored locally (encrypted) 18 * - Generation calls go DIRECTLY to OpenRouter (not proxied through broker) 19 * - Broker is only used for OAuth flow and model list 16 * Follows the SAME pattern as Canva/Figma/Dropbox: 17 * - Broker handles OAuth and stores the persistent credential (API key) 18 * - Plugin receives only a broker JWT during pickup 19 * - Plugin calls broker's /token/refresh to get a short-lease API key 20 * - Short-lease key is stored as a WordPress transient (auto-expires in 1hr) 21 * - When transient expires, plugin requests a fresh key from broker 22 * - No long-lived credentials in the WordPress database 20 23 * 21 24 * Flow: … … 23 26 * 2. Broker redirects to openrouter.ai/auth (PKCE) 24 27 * 3. User authenticates → code returned to broker 25 * 4. Broker exchanges code for API key, encrypts & stores 28 * 4. Broker exchanges code for API key, encrypts & stores on broker 26 29 * 5. User redirected back to WP with ?lsp_openrouter_connected=1 27 * 6. Plugin picks up broker JWT AND API key28 * 7. Generation: plugin → OpenRouter directly (with API key)29 * 8. Models: plugin → broker (with JWT) for curated list30 * 6. Plugin picks up broker JWT (no API key in response) 31 * 7. Generation: plugin calls broker /token/refresh → gets short-lease key → calls OpenRouter directly 32 * 8. Models: plugin → OpenRouter public API (no auth needed) 30 33 */ 31 34 class OpenRouterOAuth { … … 124 127 ]; 125 128 126 // Store API key for direct OpenRouter calls (skips broker proxy) 127 if (!empty($data['data']['api_key'])) { 128 $opts['openrouter_api_key_enc'] = Crypto::enc($data['data']['api_key']); 129 error_log('[LSP OpenRouter] ✓ API key received and encrypted'); 130 } else { 131 error_log('[LSP OpenRouter] ⚠ No API key in pickup response — only broker token'); 132 } 129 // Clean up any legacy stored API key from pre-refresh architecture 130 $opts['openrouter_api_key_enc'] = ''; 133 131 134 132 Admin::set_opt($opts); 135 133 136 error_log('[LSP OpenRouter] ✓ Successfully connected and stored credentials'); 134 // Clear any stale short-lease key transient 135 delete_transient('lsp_openrouter_access_token'); 136 137 error_log('[LSP OpenRouter] ✓ Connected — broker token stored, API key stays on broker'); 137 138 set_transient('lsp_openrouter_notice', 'success', 60); 138 139 … … 165 166 166 167 /** 167 * Get the OpenRouter API key for direct calls ( bypasses broker proxy)168 * Get the OpenRouter API key for direct calls (short-lease from broker) 168 169 * 169 * If not cached locally, fetches from broker using the broker JWT. 170 * This ensures generation requests go directly to OpenRouter, not through our server. 170 * Mirrors the Canva/Figma/Dropbox token refresh pattern: 171 * 1. Check WordPress transient (cached short-lease key, ≤1 hour) 172 * 2. If expired, call broker's /openrouter/token/refresh endpoint 173 * 3. Broker decrypts stored API key and returns it with TTL 174 * 4. Store in transient (auto-expires) 175 * 176 * Result: no long-lived credentials in the WordPress database. 171 177 * 172 178 * @return string|WP_Error API key or error 173 179 */ 174 180 public static function get_api_key() { 175 $o = Admin::get_opt(); 176 177 // Try locally stored key first 178 $enc = $o['openrouter_api_key_enc'] ?? ''; 179 if ($enc) { 180 $key = Crypto::dec($enc); 181 if ($key) return $key; 182 } 183 184 // Not stored yet (existing connection from before direct mode) — fetch from broker 181 // 1. Check transient first (cached short-lease key) 182 $cached = get_transient('lsp_openrouter_access_token'); 183 if ($cached) { 184 return $cached; 185 } 186 187 // 2. Get broker token for authentication 185 188 $broker_token = self::get_broker_token(); 186 189 if (is_wp_error($broker_token)) { … … 188 191 } 189 192 190 $resp = wp_remote_get( 191 self::BROKER_URL . '/wp-json/lsp-broker/v1/openrouter/get-key', 193 // 3. Request fresh short-lease key from broker 194 $resp = wp_remote_post( 195 self::BROKER_URL . '/wp-json/lsp-broker/v1/openrouter/token/refresh', 192 196 [ 193 197 'timeout' => 15, … … 200 204 201 205 if (is_wp_error($resp)) { 202 return new \WP_Error('key_fetch_failed', 'Could not retrieve API key: ' . $resp->get_error_message()); 203 } 204 206 return new \WP_Error('token_refresh_failed', 'Could not refresh OpenRouter key: ' . $resp->get_error_message()); 207 } 208 209 $http_code = wp_remote_retrieve_response_code($resp); 205 210 $data = json_decode(wp_remote_retrieve_body($resp), true); 206 211 207 if (empty($data['success']) || empty($data['data']['api_key'])) { 208 $error = $data['data']['error'] ?? 'Unknown error retrieving API key'; 209 return new \WP_Error('key_fetch_failed', $error); 210 } 211 212 // Cache locally for future calls 213 Admin::set_opt([ 214 'openrouter_api_key_enc' => Crypto::enc($data['data']['api_key']), 215 ]); 216 217 return $data['data']['api_key']; 212 // Handle broker errors 213 if ($http_code !== 200 || empty($data['success']) || empty($data['data']['access_token'])) { 214 $error = $data['data']['error'] ?? 'Unknown error refreshing OpenRouter key'; 215 $reconnect = !empty($data['data']['reconnect']); 216 217 Logger::debug('[LSP OpenRouter] Token refresh failed: ' . $error . ($reconnect ? ' (reconnect needed)' : '')); 218 219 return new \WP_Error( 220 $reconnect ? 'openrouter_reconnect' : 'token_refresh_failed', 221 $error 222 ); 223 } 224 225 // 4. Store as transient — auto-expires, never persists in options table 226 $key = $data['data']['access_token']; 227 $ttl = (int)($data['data']['expires_in'] ?? 3600); 228 229 // Refresh 60s early to avoid hitting OpenRouter with a stale key 230 set_transient('lsp_openrouter_access_token', $key, max($ttl - 60, 300)); 231 232 Logger::debug('[LSP OpenRouter] Short-lease key refreshed, TTL=' . $ttl . 's'); 233 234 return $key; 218 235 } 219 236 … … 239 256 Admin::set_opt([ 240 257 'openrouter_broker_token_enc' => '', 241 'openrouter_api_key_enc' => '', 258 'openrouter_api_key_enc' => '', // Clear any legacy stored key 242 259 'openrouter_connected_at' => 0, 243 260 ]); 244 261 245 // Clear cached models 262 // Clear short-lease key transient and cached models 263 delete_transient('lsp_openrouter_access_token'); 246 264 delete_transient('lsp_openrouter_models_v3'); 247 265 248 Logger::debug('[LSP OpenRouter] Disconnected – broker token cleared');266 Logger::debug('[LSP OpenRouter] Disconnected – broker token and transients cleared'); 249 267 } 250 268
Note: See TracChangeset
for help on using the changeset viewer.