logggai
Version:
AI-powered CLI for transforming your development work into professional content
157 lines ⢠7.09 kB
JavaScript
;
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