From c29975f23f442eacbd2effe4def9d1aca9174706 Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Sun, 14 Dec 2025 13:40:48 +0400 Subject: [PATCH] Auth: Added launcher auth check, triggers OAuth before menu if not authenticated --- OpenQode.bat | 5 ++ OpenQode.sh | 5 ++ bin/auth-check.mjs | 186 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 bin/auth-check.mjs diff --git a/OpenQode.bat b/OpenQode.bat index 3750383..33db39c 100644 --- a/OpenQode.bat +++ b/OpenQode.bat @@ -22,6 +22,11 @@ if not exist "node_modules" ( echo. ) +REM --- Auth Check --- +echo [INFO] Checking Qwen authentication... +node bin\auth-check.mjs +echo. + :menu cls echo ======================================== diff --git a/OpenQode.sh b/OpenQode.sh index 37a05d3..9c72fd1 100644 --- a/OpenQode.sh +++ b/OpenQode.sh @@ -20,6 +20,11 @@ if [ ! -d "node_modules" ]; then echo "" fi +# Check Qwen authentication +echo "[INFO] Checking Qwen authentication..." +node bin/auth-check.mjs +echo "" + # Functions pause() { read -p "Press Enter to continue..." diff --git a/bin/auth-check.mjs b/bin/auth-check.mjs new file mode 100644 index 0000000..0cf6e90 --- /dev/null +++ b/bin/auth-check.mjs @@ -0,0 +1,186 @@ +#!/usr/bin/env node +/** + * OpenQode Auth Check + * Verifies Qwen authentication and triggers OAuth if needed. + * Called by launchers before showing menu. + * + * Exit codes: + * 0 = Authenticated + * 1 = Auth failed + * 2 = User cancelled + */ +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); + +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; +import { exec } from 'child_process'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const ROOT = path.resolve(__dirname, '..'); + +// Colors +const C = { + reset: '\x1b[0m', + cyan: '\x1b[36m', + green: '\x1b[32m', + yellow: '\x1b[33m', + red: '\x1b[31m', + magenta: '\x1b[35m', + bold: '\x1b[1m', + dim: '\x1b[2m' +}; + +// Token file paths to check +const TOKEN_PATHS = [ + path.join(ROOT, 'tokens.json'), + path.join(ROOT, '.qwen-tokens.json'), + path.join(process.env.HOME || process.env.USERPROFILE || '', '.qwen', 'config.json') +]; + +// Check if we have valid tokens +const checkExistingAuth = () => { + for (const tokenPath of TOKEN_PATHS) { + try { + if (fs.existsSync(tokenPath)) { + const data = JSON.parse(fs.readFileSync(tokenPath, 'utf8')); + if (data.access_token) { + // Check if expired (if expiry info available) + if (data.expires_at) { + const expiry = new Date(data.expires_at); + if (expiry > new Date()) { + return { valid: true, source: tokenPath }; + } + } else { + // No expiry info, assume valid + return { valid: true, source: tokenPath }; + } + } + } + } catch (e) { /* ignore */ } + } + return { valid: false }; +}; + +// Open URL in default browser +const openBrowser = (url) => { + const platform = process.platform; + let cmd; + + switch (platform) { + case 'darwin': + cmd = `open "${url}"`; + break; + case 'win32': + cmd = `start "" "${url}"`; + break; + default: + cmd = `xdg-open "${url}"`; + } + + exec(cmd, (err) => { + if (err) console.log(C.yellow + ' (Could not open browser automatically)' + C.reset); + }); +}; + +// Perform OAuth device flow +const performAuth = async () => { + console.log(C.cyan + '\n Starting Qwen OAuth...' + C.reset); + + try { + const { QwenOAuth } = await import('../qwen-oauth.mjs'); + const oauth = new QwenOAuth(); + + // Start device flow + const deviceInfo = await oauth.startDeviceFlow(); + + console.log(''); + console.log(C.magenta + ' ╔═══════════════════════════════════════════╗' + C.reset); + console.log(C.magenta + ' ║ QWEN AUTHENTICATION ║' + C.reset); + console.log(C.magenta + ' ╚═══════════════════════════════════════════╝' + C.reset); + console.log(''); + console.log(C.yellow + ' 1. Open this URL in your browser:' + C.reset); + console.log(C.cyan + ` ${deviceInfo.verificationUriComplete || deviceInfo.verificationUri}` + C.reset); + console.log(''); + if (deviceInfo.userCode) { + console.log(C.yellow + ' 2. Enter this code if prompted:' + C.reset); + console.log(C.green + C.bold + ` ${deviceInfo.userCode}` + C.reset); + console.log(''); + } + console.log(C.dim + ' Waiting for you to complete login in browser...' + C.reset); + + // Try to open browser automatically + openBrowser(deviceInfo.verificationUriComplete || deviceInfo.verificationUri); + + // Poll for tokens + const tokens = await oauth.pollForTokens(); + + if (tokens && tokens.access_token) { + // Save tokens + oauth.saveTokens(tokens); + + // Also save to main tokens.json for compatibility + const mainTokenPath = path.join(ROOT, 'tokens.json'); + fs.writeFileSync(mainTokenPath, JSON.stringify({ + access_token: tokens.access_token, + refresh_token: tokens.refresh_token, + expires_at: tokens.expires_at || new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() + }, null, 2)); + + console.log(C.green + '\n ✅ Authentication successful!' + C.reset); + return true; + } else { + console.log(C.red + '\n ✗ Authentication failed or timed out.' + C.reset); + return false; + } + } catch (e) { + console.log(C.red + `\n ✗ OAuth error: ${e.message}` + C.reset); + + // Provide helpful guidance based on error + if (e.message.includes('Client ID')) { + console.log(C.yellow + '\n To fix this:' + C.reset); + console.log(C.dim + ' 1. Copy config.example.cjs to config.cjs' + C.reset); + console.log(C.dim + ' 2. Add your QWEN_OAUTH_CLIENT_ID' + C.reset); + } + + return false; + } +}; + +// Main +const main = async () => { + console.log(''); + console.log(C.cyan + ' ╔═══════════════════════════════════════════╗' + C.reset); + console.log(C.cyan + ' ║ OpenQode Authentication Check ║' + C.reset); + console.log(C.cyan + ' ╚═══════════════════════════════════════════╝' + C.reset); + console.log(''); + console.log(C.dim + ' Checking Qwen authentication status...' + C.reset); + + // Check existing auth + const authStatus = checkExistingAuth(); + + if (authStatus.valid) { + console.log(C.green + '\n ✅ Already authenticated!' + C.reset); + console.log(C.dim + ` Token source: ${path.basename(authStatus.source)}` + C.reset); + process.exit(0); + } + + console.log(C.yellow + '\n [!] Not authenticated. Starting OAuth...' + C.reset); + + const success = await performAuth(); + + if (success) { + console.log(C.green + '\n Ready to use OpenQode!' + C.reset); + process.exit(0); + } else { + console.log(C.yellow + '\n You can still use OpenQode, but AI features may be limited.' + C.reset); + process.exit(1); + } +}; + +main().catch(e => { + console.error(C.red + `Auth check failed: ${e.message}` + C.reset); + process.exit(1); +});