UNPKG

logggai

Version:

AI-powered CLI for transforming your development work into professional content

157 lines • 7.09 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.loginCommand = loginCommand; const chalk = require("chalk"); const ora_1 = require("ora"); const axios_1 = require("axios"); const crypto = require("crypto"); const nanoid_1 = require("nanoid"); const inquirer_1 = require("inquirer"); const { setConfig, getConfig, isLoggedIn } = require('../lib/config'); // Helper functions for OAuth2 PKCE function generateCodeVerifier() { return (0, nanoid_1.nanoid)(128); } function generateCodeChallenge(verifier) { const hash = crypto.createHash('sha256').update(verifier).digest('base64'); return hash.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''); } async function loginCommand(force = false) { console.log(chalk.default.blue.bold('šŸ” Login to Logggai')); console.log(); // If not forced, check if already logged in if (!force && isLoggedIn()) { const userEmail = getConfig('userEmail'); console.log(chalk.default.green('āœ… Already logged in!')); if (userEmail) { console.log(chalk.default.gray(`Logged in as: ${userEmail}`)); } console.log(chalk.default.cyan('Use --force to login again')); return; } const apiUrl = getConfig('apiUrl'); const spinner = (0, ora_1.default)('Preparing OAuth2 authentication...').start(); try { // Generate PKCE parameters const codeVerifier = generateCodeVerifier(); const codeChallenge = generateCodeChallenge(codeVerifier); const state = (0, nanoid_1.nanoid)(32); // OAuth2 parameters const clientId = 'cli'; // Add CLI to allowed clients const redirectUri = `${apiUrl}/callback`; // Use SaaS callback page spinner.succeed(chalk.default.green('OAuth2 parameters generated!')); console.log(); console.log(chalk.default.yellow.bold('šŸ” Please authorize this application:')); // Build authorization URL const authUrl = new URL(`${apiUrl}/api/oauth/authorize`); authUrl.searchParams.set('client_id', clientId); authUrl.searchParams.set('redirect_uri', redirectUri); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('code_challenge', codeChallenge); authUrl.searchParams.set('code_challenge_method', 'S256'); authUrl.searchParams.set('state', state); authUrl.searchParams.set('scope', 'openid profile email orgs'); console.log(chalk.default.cyan(` Open: ${authUrl.toString()}`)); console.log(); console.log(chalk.default.cyan('You can register with GitHub or email')); // Start local server to receive callback const authSpinner = (0, ora_1.default)('Waiting for authorization in browser...').start(); const authCode = await waitForAuthorizationCode(redirectUri, state, authSpinner); if (!authCode) { authSpinner.fail(chalk.default.red('āŒ Authorization failed')); console.log(chalk.default.yellow('Authorization was denied or timed out')); process.exit(1); } authSpinner.text = 'Exchanging authorization code for token...'; // Exchange authorization code for access token const tokenResponse = await axios_1.default.post(`${apiUrl}/api/oauth/token`, { grant_type: 'authorization_code', client_id: clientId, code: authCode, redirect_uri: redirectUri, code_verifier: codeVerifier }, { headers: { 'Content-Type': 'application/json' } }); const tokenData = tokenResponse.data; // Save tokens setConfig('sessionToken', tokenData.access_token); setConfig('tokenExpiresAt', Date.now() + (tokenData.expires_in * 1000)); setConfig('userId', tokenData.user_id); // Get user info to save email try { const userResponse = await axios_1.default.get(`${apiUrl}/api/user/context`, { headers: { 'Authorization': `Bearer ${tokenData.access_token}`, 'x-clerk-user-id': tokenData.user_id } }); // Save email if available if (userResponse.data?.user?.email) { setConfig('userEmail', userResponse.data.user.email); } authSpinner.succeed(chalk.default.green('āœ… Login successful!')); console.log(chalk.default.gray(`Successfully authenticated as: ${userResponse.data?.user?.email || 'User'}`)); console.log(); console.log(chalk.default.green.bold('šŸŽ‰ You can now use:')); console.log(chalk.default.cyan(' logggai post "My new article"')); console.log(chalk.default.cyan(' logggai list')); return; } catch (error) { authSpinner.succeed(chalk.default.green('āœ… Authentication successful!')); console.log(chalk.default.gray('Successfully authenticated')); console.log(); console.log(chalk.default.green.bold('šŸŽ‰ You can now use the CLI!')); return; } } catch (error) { spinner.fail(chalk.default.red('āŒ Authentication error')); if (error.code === 'ECONNREFUSED') { console.log(chalk.default.red('Unable to connect to Logggai server')); console.log(chalk.default.yellow(`Check that the server is running on: ${apiUrl}`)); } else { console.log(chalk.default.red('Error during authentication:')); console.log(chalk.default.red(error.message)); if (error.response?.data) { console.log(chalk.default.red('Server response:', JSON.stringify(error.response.data, null, 2))); } } process.exit(1); } } // Function to wait for authorization code from user input async function waitForAuthorizationCode(redirectUri, expectedState, spinner) { // Stop the spinner to allow inquirer to display properly if (spinner) { spinner.stop(); } console.log(chalk.default.yellow('\nšŸ“‹ After authorizing, you will be redirected to a page showing your authorization code.')); console.log(chalk.default.yellow('Please copy the authorization code from that page and paste it below.')); console.log(); try { const answers = await inquirer_1.default.prompt([ { type: 'input', name: 'authCode', message: 'Enter the authorization code:', validate: (input) => { if (!input || input.trim().length === 0) { return 'Please enter the authorization code'; } return true; } } ]); return answers.authCode.trim(); } catch (error) { console.log(chalk.default.yellow('\nāŒ Authorization cancelled')); return null; } } //# sourceMappingURL=login.js.map