UNPKG

ryuu

Version:

Domo App Dev Studio CLI, The main tool used to create, edit, and publish app designs to Domo

293 lines 13.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const chalk = require('chalk'); const fs = __importStar(require("fs-extra")); const Domo = require("ryuu-client"); const login_1 = require("../util/login"); const BaseCommand_1 = require("../util/BaseCommand"); const prompts_1 = require("../util/prompts"); /** * Validates a developer token. * Tokens should be alphanumeric strings (letters and numbers only). * Example: DDCI27236b8d8690ab86cf5328c41a94d068aca1cde4eb6edc34 */ function validateToken(token) { if (!token || token.trim().length === 0) { return { valid: false, error: 'Token cannot be empty' }; } const trimmedToken = token.trim(); // Check minimum length (tokens should be reasonably long) if (trimmedToken.length < 20) { return { valid: false, error: 'Token is too short. Please verify you entered the complete token.', }; } // Developer token format: alphanumeric only (letters and numbers) const tokenRegex = /^[a-zA-Z0-9]+$/; if (tokenRegex.test(trimmedToken)) { return { valid: true }; } return { valid: false, error: 'Invalid token format. Token should contain only letters and numbers.', }; } /** * Validates the Domo instance domain. */ function validateInstance(instance) { const acceptedDomains = ['domo.com', 'domotech.io', 'domorig.io']; return acceptedDomains.some(domain => instance.endsWith(domain)); } class LoginCommand extends BaseCommand_1.BaseCommand { constructor(program) { super(program); } configure() { this.program .command('login') .description('Login to Domo using OAuth or token-based authentication') .option('-i, --instance <value>', 'Domo instance hostname') .option('-u, --user-email <value>', 'User email address') .option('-f, --manifest-file <file>', 'Use a specific manifest file') .option('-t, --token <value>', 'Developer token for authentication') .option('--token-only', 'Force token-only authentication mode (will prompt for token)') .option('--migrate', 'Migrate login files from ryuu 3.x') .option('--no-upgrade-check', 'Do not check for new versions') .option('--upgrade-download', 'Automatically download newer version if available') .action(this.run.bind(this)); } /** * Handles token-based authentication. */ async handleTokenAuth(instance, token) { // Validate token format (must be GUID) const validation = validateToken(token); if (!validation.valid) { throw new Error(validation.error); } // Validate instance if (!validateInstance(instance)) { console.log(''); console.log(chalk.yellow('⚠️ Warning: Unexpected Domo instance format')); console.log(chalk.yellow(` Instance entered: ${chalk.bold(instance)}`)); console.log(chalk.yellow(' Expected format: instance should end with .domo.com, .domotech.io, or .domorig.io')); console.log(''); const shouldContinue = await (0, prompts_1.createConfirm)('Do you want to continue with this instance anyway?', false); if (!shouldContinue) { throw new Error('Login cancelled. Please verify your instance name and try again.'); } console.log(chalk.yellow(`\nContinuing with instance: ${chalk.bold(instance)}\n`)); } // Create login instance and persist token const login = new login_1.Login(instance); // Store token with devToken flag set to true login.persistLogin(token, true); this.logOk(`Successfully authenticated with token for ${instance}`, 'Future CLI commands to this instance will not require username/password.'); } /** * Handles traditional OAuth authentication. */ async handleOAuthLogin(instance) { const login = new login_1.Login(instance); // Clear any existing devToken flag before OAuth login // This ensures OAuth flow works even if user previously used token auth if (login.login.has('devToken')) { login.login.set('devToken', false); } const client = await login.getClient(); const loginData = await client.login(); if (!loginData || !loginData.refreshToken) { throw new Error('OAuth login did not return a refresh token. The browser authentication may have timed out.'); } this.logOk(`Login to ${instance} successful. Welcome, ${loginData.displayName}.`); try { // OAuth login should always set devToken to false login.persistLogin(loginData.refreshToken, false); } catch (persistError) { console.error(chalk.red('Failed to save login credentials:'), persistError.message || persistError); console.error(chalk.yellow('You may need to check file permissions.')); throw persistError; } } /** * Handles the migration of login files from ryuu 3.x. */ handleMigrate() { const isWin = process.platform === 'win32'; const homeEnv = process.env[isWin ? 'USERPROFILE' : 'HOME']; const deprecatedLoginDir = `${homeEnv}/.domo/login/`; if (fs.existsSync(deprecatedLoginDir)) { try { fs.copySync(deprecatedLoginDir, `${Domo.getHomeDir()}/ryuu/`, { overwrite: false, }); this.logOk('Successfully transferred login files from ryuu 3.x', `Login files copied to ${Domo.getHomeDir()}/ryuu/`); } catch (error) { this.logFail('Failed to transfer logins:', error); } } else { this.logFail('You do not have any ryuu 3.x logins to transfer to ryuu 4.x.', 'Run this command without the --transfer flag to log in.'); } } /** * Determines the authentication mode based on options. */ determineAuthMode(args) { if (args.migrate || args.transfer) { return 'migrate'; } if (args.token || args.tokenOnly) { return 'token'; } // Check if there's already a stored token for the current instance try { const currentLogin = login_1.Login.getCurrentLogin(); if (currentLogin.instance) { const login = new login_1.Login(currentLogin.instance); if (login.login.get('devToken') && login.login.has('refreshToken')) { return 'token'; } } } catch (error) { // If there's an error checking for existing tokens, fall back to OAuth } return 'oauth'; } async run(args) { try { // Determine authentication mode const authMode = this.determineAuthMode(args); // Handle migrate mode if (authMode === 'migrate') { this.handleMigrate(); process.exit(0); } // Get instance - required for both auth modes let instance = args.instance; if (!instance) { instance = await (0, prompts_1.createInstanceAutocompleteWithValidation)('Domo instance (e.g. company.domo.com)', true); } // Validate instance format if (!validateInstance(instance)) { console.log(''); console.log(chalk.yellow('⚠️ Warning: Unexpected Domo instance format')); console.log(chalk.yellow(` Instance entered: ${chalk.bold(instance)}`)); console.log(chalk.yellow(' Expected format: instance should end with .domo.com, .domotech.io, or .domorig.io')); console.log(''); const shouldContinue = await (0, prompts_1.createConfirm)('Do you want to continue with this instance anyway?', false); if (!shouldContinue) { this.logFail('Login cancelled', 'Please verify your instance name and try again.'); process.exit(1); } console.log(chalk.yellow(`\nContinuing with instance: ${chalk.bold(instance)}\n`)); } // Handle token authentication if (authMode === 'token') { let token = args.token; // If no token provided, check if there's an existing stored token if (!token) { try { const currentLogin = login_1.Login.getCurrentLogin(); if (currentLogin.instance === instance) { const login = new login_1.Login(instance); if (login.login.get('devToken') && login.login.has('refreshToken')) { // Use existing token - no need to re-authenticate console.log(chalk.green(`\nUsing existing token for ${instance}\n`)); process.exit(0); } } } catch (error) { // If there's an error accessing existing tokens, continue to prompt } // If no existing token or error accessing it, prompt for new token console.log(chalk.cyan('\nToken-based authentication selected. You can find your developer token in the Admin section of Domo.\n')); const { createInput } = await Promise.resolve().then(() => __importStar(require('../util/prompts'))); token = await createInput('Developer token', '', (input) => { const validation = validateToken(input); if (!validation.valid) { return validation.error || 'Invalid token format'; } return true; }); } if (!token) { this.logFail('Token is required for token-based authentication', 'Use -t or --token to provide a token, or run without --token-only for traditional login.'); process.exit(1); } await this.handleTokenAuth(instance, token); process.exit(0); } // Handle traditional OAuth login await this.handleOAuthLogin(instance); process.exit(0); } catch (err) { // Determine if this was a token auth failure or OAuth failure const authMethod = args.token || args.tokenOnly ? 'token' : 'username/password'; if (authMethod === 'token') { this.logFail('Token authentication failed', 'Please verify your token is correct and has not expired. You can generate a new token in the Admin section of Domo.'); } else { this.logFail('Login failed', 'Please check your credentials and network connection.'); } // Log detailed error for debugging if (err.message) { console.error(chalk.red('\nError details:'), err.message); } if (process.env.DEBUG) { console.error(chalk.gray('\nFull error:'), err); if (err.stack) { console.error(chalk.gray('Stack trace:')); console.error(err.stack); } } process.exit(1); } } } module.exports = (program) => { new LoginCommand(program); }; //# sourceMappingURL=login.js.map