const axios = require('axios'); const chalk = require('chalk'); const inquirer = require('inquirer'); const { spawn } = require('child_process'); const { purpleGold, goldPink } = require('./banner'); const db = require('./db-operations'); // GitHub OAuth App credentials (you'll need to register the app) // For now, using public client credentials pattern const CLIENT_ID = 'Iv1.b507a08c87ecfe98'; // Example - you should register your own app // Initiate OAuth device flow async function authenticateWithGitHub() { console.clear(); console.log(purpleGold('\nšŸ” GITHUB AUTHENTICATION šŸ”\n')); console.log(chalk.gray('Using GitHub OAuth Device Flow for secure authentication\n')); try { // Step 1: Request device and user codes console.log(chalk.hex('#FFD700')(' Step 1: Requesting authorization codes...\n')); const deviceResponse = await axios.post( 'https://github.com/login/device/code', { client_id: CLIENT_ID, scope: 'public_repo' }, { headers: { 'Accept': 'application/json' } } ); const { device_code, user_code, verification_uri, expires_in, interval } = deviceResponse.data; // Step 2: Display user code and open browser console.log(chalk.hex('#DA22FF')(' Step 2: Complete authorization in your browser\n')); console.log(chalk.gray('━'.repeat(70))); console.log(); console.log(chalk.hex('#FF69B4')(' Visit: ') + chalk.cyan(verification_uri)); console.log(chalk.hex('#FFD700')(' Enter code: ') + chalk.bold.hex('#FFD700')(user_code)); console.log(); console.log(chalk.gray('━'.repeat(70))); console.log(); const { openBrowser } = await inquirer.prompt([ { type: 'confirm', name: 'openBrowser', message: 'Open browser automatically?', default: true } ]); if (openBrowser) { spawn('xdg-open', [verification_uri], { detached: true, stdio: 'ignore' }); console.log(chalk.green('\n āœ“ Browser opened!\n')); } console.log(chalk.hex('#9733EE')(' Waiting for authorization...')); console.log(chalk.gray(` (Code expires in ${Math.floor(expires_in / 60)} minutes)\n`)); // Step 3: Poll for access token const token = await pollForAccessToken(device_code, interval, expires_in); if (token) { // Save token db.setSetting('githubToken', token); db.setSetting('githubAuthMethod', 'oauth'); console.log(); console.log(goldPink(' ✨ Successfully authenticated! ✨\n')); console.log(chalk.green(' āœ“ Your rate limit is now 5,000 requests/hour!\n')); await new Promise(resolve => setTimeout(resolve, 2000)); return true; } return false; } catch (error) { console.error(chalk.red('\n āœ— Authentication failed:'), error.message); console.log(); // Offer fallback to manual token const { fallback } = await inquirer.prompt([ { type: 'confirm', name: 'fallback', message: 'Use manual token instead?', default: true } ]); if (fallback) { return await manualTokenInput(); } return false; } } // Poll GitHub for access token async function pollForAccessToken(deviceCode, interval, expiresIn) { const startTime = Date.now(); const pollInterval = interval * 1000; while (Date.now() - startTime < expiresIn * 1000) { await new Promise(resolve => setTimeout(resolve, pollInterval)); try { const response = await axios.post( 'https://github.com/login/oauth/access_token', { client_id: CLIENT_ID, device_code: deviceCode, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' }, { headers: { 'Accept': 'application/json' } } ); const { access_token, error } = response.data; if (access_token) { return access_token; } if (error === 'authorization_pending') { // Still waiting for user process.stdout.write(chalk.gray('.')); continue; } if (error === 'slow_down') { // GitHub asked us to slow down await new Promise(resolve => setTimeout(resolve, 5000)); continue; } if (error === 'expired_token') { console.log(chalk.red('\n āœ— Code expired. Please try again.')); return null; } if (error === 'access_denied') { console.log(chalk.yellow('\n āš ļø Authorization denied by user.')); return null; } } catch (error) { // Continue polling on network errors continue; } } console.log(chalk.red('\n āœ— Timeout waiting for authorization.')); return null; } // Fallback: Manual token input async function manualTokenInput() { console.log(); console.log(chalk.hex('#FFD700')('šŸ“ Manual Token Setup\n')); console.log(chalk.gray(' 1. Go to: https://github.com/settings/tokens')); console.log(chalk.gray(' 2. Generate new token (classic)')); console.log(chalk.gray(' 3. Select scope: public_repo')); console.log(chalk.gray(' 4. Copy and paste the token below\n')); const { token, openUrl } = await inquirer.prompt([ { type: 'confirm', name: 'openUrl', message: 'Open GitHub tokens page?', default: true }, { type: 'password', name: 'token', message: 'Paste your token:', mask: '*', validate: input => { if (!input.trim()) { return 'Token is required'; } if (!input.startsWith('ghp_') && !input.startsWith('github_pat_')) { return 'Invalid token format'; } return true; } } ]); if (openUrl) { spawn('xdg-open', ['https://github.com/settings/tokens'], { detached: true, stdio: 'ignore' }); } if (token && token.trim()) { db.setSetting('githubToken', token.trim()); db.setSetting('githubAuthMethod', 'manual'); console.log(chalk.green('\n āœ“ Token saved!\n')); return true; } return false; } // Check authentication status function isAuthenticated() { const token = db.getSetting('githubToken', null); return token && token !== 'null'; } // Get authentication method function getAuthMethod() { return db.getSetting('githubAuthMethod', 'none'); } // Revoke/logout async function logout() { const method = getAuthMethod(); console.log(); const { confirm } = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: `Remove ${method === 'oauth' ? 'OAuth' : 'manually added'} token?`, default: false } ]); if (confirm) { db.setSetting('githubToken', 'null'); db.setSetting('githubAuthMethod', 'none'); console.log(chalk.green('\n āœ“ Token removed. Rate limit back to 60/hour.\n')); if (method === 'oauth') { console.log(chalk.gray(' Note: Token is revoked locally. For complete security,')); console.log(chalk.gray(' revoke at: https://github.com/settings/applications\n')); } } } module.exports = { authenticateWithGitHub, manualTokenInput, isAuthenticated, getAuthMethod, logout };