UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

571 lines 21.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.extraProfile = exports.Login = exports.ProfileCmd = void 0; const _ = require("lodash"); const express = require("express"); const jose = require("jose"); const options_1 = require("./options"); const generic_1 = require("./generic"); const format_1 = require("../format/format"); const objects_1 = require("../util/objects"); const models_1 = require("../format/models"); const names_1 = require("../util/names"); const command_1 = require("../cli/command"); const logger_1 = require("../util/logger"); const client_1 = require("../session/client"); const session_1 = require("../session/session"); const functions_1 = require("../util/functions"); const uuid_1 = require("uuid"); const defaults = { format: { color: true, max: 50, output: 'text', ts: 'age', }, request: { endpoint: 'https://api.cpln.io', insecure: false, timeout: 30, }, }; class ProfileCmd extends command_1.Command { constructor() { super(...arguments); this.command = 'profile'; this.describe = 'Manage local profiles'; } builder(yargs) { return yargs .demandCommand() .help() .command(new Get().toYargs()) .command(new Delete().toYargs()) .command(new Login().toYargs()) .command(new Update().toYargs()) .command(new Token().toYargs()) .command(new SetDefault().toYargs()); } handle() { } } exports.ProfileCmd = ProfileCmd; class SetDefault extends command_1.Command { constructor() { super(...arguments); this.command = 'set-default <profile>'; this.describe = 'Set the referenced profile as the default'; } builder(yargs) { return yargs.positional('profile', { describe: 'Profile name to be set as the default', demandOption: true, }); } async handle(args) { const profile = this.env.profileManager.find(args.profile); if (profile) { this.env.profileManager.storeDefault(args.profile); } else { (0, format_1.write)(this.env.err, `Profile ${args.profile} does not exist\n`); process.exit(1); } } } class Get extends command_1.Command { constructor() { super(...arguments); this.command = 'get [profiles...]'; this.describe = 'Retrieve one or more profiles'; } builder(yargs) { return (0, functions_1.pipe)( // () => { return yargs.positional('profiles', { describe: 'One or more profiles to show', demandOption: true, }); }, options_1.withStandardOptions)(yargs); } handle(args) { var _a, _b; if (!((_a = args.profiles) === null || _a === void 0 ? void 0 : _a.length)) { const allProfiles = this.env.profileManager.list().map(censorProfile); showProfile(this.env.out, allProfiles, args, this.env); return; } const res = []; if ((_b = args.profiles) === null || _b === void 0 ? void 0 : _b.includes('.')) { const def = this.env.profileManager.readDefault(); if (def) { args.profiles.push(def); } } args.profiles = _.uniq(args.profiles); for (let pfName of args.profiles) { const pr = this.env.profileManager.find(pfName); if (pr) { res.push(censorProfile(pr)); } } showProfile(this.env.out, res, args, this.env); } } class Delete extends command_1.Command { constructor() { super(...arguments); this.command = 'delete <profiles...>'; this.describe = 'Delete one or more referenced profiles'; } builder(yargs) { return (0, functions_1.pipe)( // (yargs) => { return yargs.positional('profile', { describe: 'One or more profile names to delete', demandOption: true, }); }, options_1.withStandardOptions)(yargs); } async handle(args) { const deleted = []; for (let pfName of args.profiles) { const pr = this.env.profileManager.delete(pfName); if (pr) { deleted.push(censorProfile(pr)); } } showProfile(this.env.out, deleted, args, this.env); } } class Update extends command_1.Command { constructor() { super(...arguments); this.command = 'update <profile>'; this.describe = 'Manage the referenced profile'; this.aliases = ['create']; } builder(yargs) { return (0, functions_1.pipe)( // (yargs) => { return yargs .positional('profile', { describe: 'Name of the profile to update. If it does not exist, a new profile with that name will be created', demandOption: true, }) .options({ login: { description: 'Launch a browser to begin the interactive login process using the referenced profile', conflicts: 'token', }, default: { description: 'Set the given profile as the default', }, // XXX duplicate the org/gvc options here because we want to hide --profile org: { requiresArg: true, description: 'Set the given organization as the default organization of the referenced profile', }, gvc: { requiresArg: true, description: 'Set the given GVC as the default GVC of the referenced profile', }, }); }, options_1.withStandardOptions)(yargs); } async handle(args) { var _a, _b, _c, _d, _e, _f; let profileName = args.profile; if (profileName == '.') { profileName = this.env.profileManager.readDefault(); } else if (!profileName) { profileName = this.env.profileManager.guessDefault(); } let profile = this.env.profileManager.find(profileName); const tokenServerInfo = await getTokenServerInfo((profile === null || profile === void 0 ? void 0 : profile.name) || args.profile); if (!profile) { logger_1.logger.debug('Creating profile ' + args.profile); profile = { name: args.profile, context: {}, request: {}, format: {}, tokenServer: tokenServerInfo, }; } else { // in case someone messes with a profile on disk profile.context = (_a = profile.context) !== null && _a !== void 0 ? _a : {}; profile.request = (_b = profile.request) !== null && _b !== void 0 ? _b : {}; profile.format = (_c = profile.format) !== null && _c !== void 0 ? _c : {}; profile.tokenServer = (_d = profile.tokenServer) !== null && _d !== void 0 ? _d : tokenServerInfo; } profile.context.profile = args.profile; profile.request = (0, objects_1.merge)(defaults.request, profile.request, (0, objects_1.pick)(args, 'endpoint', 'insecure', 'timeout', 'token')); profile.format = (0, objects_1.merge)(defaults.format, profile.format, (0, objects_1.pick)(args, 'color', 'max', 'output', 'ts')); // Update profile context if (args.org !== undefined) { profile.context.org = args.org; } if (args.gvc !== undefined) { profile.context.gvc = args.gvc; } // If user changes the org, without specifying the gvc, then the new org probably doesn't have a gvc with the same name as the old default gvc, so we set it to empty if (args.org !== undefined && !args.gvc) { logger_1.logger.info('GVC unset in profile\n'); profile.context.gvc = ''; } if (args.login) { const loginMod = require('../login/login'); try { await loginMod.doInteractiveLogin(profile); } catch (e) { // save profile because we removed tokens and email this.env.profileManager.update(profile); console.error('Failed to login'); // Handle any unexpected error if (e) { console.error(e); } return; } // if login, delete the static token delete profile.request.token; } // looks like SA token if ((_e = args.token) === null || _e === void 0 ? void 0 : _e.startsWith('s')) { const p = (0, names_1.parseSaToken)(args.token); // a valid SA token if (p) { profile.authInfo = { email: `[${p.name}]`, accessToken: '', refreshToken: '', }; profile.context.org = p.org; if (!args.gvc) { delete profile.context.gvc; } } else { // some token that is not SA // delete any refs to firebase auth state delete profile.authInfo; } } else if (args.token) { // token is provided if ((_f = args.token) === null || _f === void 0 ? void 0 : _f.startsWith('ey')) { logger_1.logger.info('The token you are saving to your profile looks like a user access token. It will expire at some point.'); } // delete any refs to firebase auth state delete profile.authInfo; } // save changes this.env.profileManager.update(profile); // update new default if given as arg, or this is the first user if (args.default || this.env.profileManager.list().length === 1) { this.env.profileManager.storeDefault(profile.name); } // display showProfile(this.env.out, censorProfile(profile), args, this.env); } } class Login extends command_1.Command { constructor() { super(...arguments); this.command = 'login [ref]'; this.describe = 'Create a profile named "default" and launch a browser to begin the interactive login process'; } // aliases = ['login']; builder(yargs) { return (0, functions_1.pipe)( // options_1.withDebugOptions, generic_1.withSingleRef)(yargs); } async handle(args) { var _a, _b, _c, _d, _e, _f, _g; let profileName = (_a = args.ref) !== null && _a !== void 0 ? _a : 'default'; let profile; const tokenServerInfo = await getTokenServerInfo(profileName); if (args.ref) { profileName = args.ref; profile = this.env.profileManager.find(profileName); if (!profile) { return this.session.abort({ message: `No profile found with name: ${profileName}` }); } // Ensuring fields if someone messes with the profile on disk profile.name = (_b = profile.name) !== null && _b !== void 0 ? _b : profileName; profile.context = (_c = profile.context) !== null && _c !== void 0 ? _c : {}; profile.request = (_d = profile.request) !== null && _d !== void 0 ? _d : {}; profile.format = (_e = profile.format) !== null && _e !== void 0 ? _e : {}; profile.tokenServer = (_f = profile.tokenServer) !== null && _f !== void 0 ? _f : tokenServerInfo; } else { logger_1.logger.debug('Creating profile named default'); profileName = 'default'; profile = { name: profileName, context: {}, request: {}, format: {}, tokenServer: tokenServerInfo, }; } profile.tokenServer = (_g = profile.tokenServer) !== null && _g !== void 0 ? _g : tokenServerInfo; profile.request = (0, objects_1.merge)(defaults.request, profile.request); profile.format = (0, objects_1.merge)(defaults.format, profile.format); const loginMod = require('../login/login'); try { await loginMod.doInteractiveLogin(profile); } catch (e) { // save profile because we removed tokens and email this.env.profileManager.update(profile); console.error('Failed to login'); // Handle any unexpected error if (e) { console.error(e); } return; } // if login, delete the static token delete profile.request.token; // save changes this.env.profileManager.update(profile); // update new default this.env.profileManager.storeDefault(profile.name); // display showProfile(this.env.out, censorProfile(profile), args, this.env); } } exports.Login = Login; class Token extends command_1.Command { constructor() { super(...arguments); this.command = 'token [profile]'; this.describe = 'Show the JWT token of the referenced profile'; } builder(yargs) { return (0, functions_1.pipe)( // () => { return yargs.positional('profile', { describe: 'Profile to retrieve the token for', demandOption: true, }); }, () => { return yargs.options({ serve: { describe: 'Serve token on a local server', boolean: true, }, port: { describe: 'Port for the local server', number: true, default: 43200, }, jwt: { describe: 'View your jwt for the token server', boolean: true, }, generateToken: { describe: 'Generate a new jwt token for the token server', boolean: true, }, }); }, options_1.withStandardOptions)(yargs); } async handle(args) { var _a, _b, _c; if (args.generateToken) { const tokenServerInfo = await getTokenServerInfo(this.session.profile.name); this.session.profile.tokenServer = tokenServerInfo; this.session.env.profileManager.update(this.session.profile); const code = tokenServerInfo.code; this.session.out(`Your token is: ${code}`); this.session.out(`Send this as bearer token to this server`); return; } if (args.jwt) { this.session.out(`Bearer ${this.session.profile.tokenServer.code}`); return; } if (args.serve) { this.createTokenServer(args.port); return; } let profileName = args.profile; if (profileName == '.') { profileName = this.env.profileManager.readDefault(); } else if (!profileName) { profileName = this.env.profileManager.guessDefault(); } // create session explicitly const session = (0, session_1.newDefaultSession)(this.env, { profile: profileName, }); // force token refresh const client = (0, client_1.makeSessionClient)(session); try { await client.get('/bad-url'); } catch (_d) { } // static tokens are in the request obj, prefer them if set session.out((_c = (_a = session.profile.request.token) !== null && _a !== void 0 ? _a : (_b = session.profile.authInfo) === null || _b === void 0 ? void 0 : _b.accessToken) !== null && _c !== void 0 ? _c : '<no token>'); } async createTokenServer(port) { const app = express(); app.get('/:profile/token', async (req, res) => { var _a, _b, _c; const { profile: profileName } = req.params; const profile = this.env.profileManager.find(profileName); if (!profile) { return res.status(404).send('No profile found'); } const profileCode = (_a = profile.tokenServer) === null || _a === void 0 ? void 0 : _a.code; if (!profileCode) { const tokenServerInfo = await getTokenServerInfo(profileName); profile.tokenServer = tokenServerInfo; this.session.env.profileManager.update(profile); } let jwt = req.headers.authorization || ''; if (jwt.includes('Bearer ')) { jwt = jwt.split('Bearer ')[1]; } if (profileCode !== jwt) { return res.status(401).send('Unauthorized'); } const secret = new TextEncoder().encode(profile.tokenServer.secret); try { const { payload } = await jose.jwtVerify(jwt, secret, { issuer: 'cpln:issuer', audience: 'cpln:audience', }); if (payload.name !== profile.name) { return res.status(401).send('Unauthorized'); } } catch (e) { return res.status(401).send('Unauthorized'); } this.session.out(`Token server received request for /${profileName}/token`); const session = (0, session_1.newDefaultSession)(this.env, { profile: profileName, }); const client = (0, client_1.makeSessionClient)(session); try { await client.get('/bad-url'); } catch (_d) { } const token = (_b = session.profile.request.token) !== null && _b !== void 0 ? _b : (_c = session.profile.authInfo) === null || _c === void 0 ? void 0 : _c.accessToken; if (!token) { return res.status(401).send('Login again'); } return res.status(200).send(token); }); app.listen(port, () => { this.session.out(`Token server is listening @ http://localhost:${port}`); }); } } /** * delete sensitive data from the output * @param pf */ function censorProfile(pf) { var _a, _b; // @ts-ignore (_a = pf.authInfo) === null || _a === void 0 ? true : delete _a.accessToken; // @ts-ignore (_b = pf.authInfo) === null || _b === void 0 ? true : delete _b.refreshToken; pf.request.token = '******'; return pf; } function showProfile(stream, obj, options, env) { // pass extra hints for the profile const extendedOpts = { ...options, _hints: { // there must be function model: extraProfile, profileManager: env.profileManager, }, }; (0, format_1.write)(stream, (0, format_1.format)(obj, extendedOpts)); } function extraProfile(options) { var _a; let mgr = (_a = options._hints) === null || _a === void 0 ? void 0 : _a.profileManager; function isDefault(name) { if (!name || !mgr) { return ' '; } if (mgr.readDefault() === name) { return '*'; } return ' '; } function isActive(name) { if (!name || !mgr) { return ' '; } if (mgr.guessDefault() === name) { return '*'; } return ' '; } return { columns: [ // (0, models_1.named)('PROFILE'), (0, models_1.named)('EMAIL'), (0, models_1.named)('DEFAULT'), (0, models_1.named)('ACTIVE'), (0, models_1.named)('ENDPOINT'), (0, models_1.named)('ORG'), (0, models_1.named)('GVC'), (0, models_1.named)('OUTPUT'), (0, models_1.named)('TIMESTAMPS'), ], makeRow(obj) { var _a, _b, _c, _d, _e, _f; return [ // obj.name, (_a = obj.authInfo) === null || _a === void 0 ? void 0 : _a.email, isDefault(obj.name), isActive(obj.name), (_b = obj.request) === null || _b === void 0 ? void 0 : _b.endpoint, (_c = obj.context) === null || _c === void 0 ? void 0 : _c.org, (_d = obj.context) === null || _d === void 0 ? void 0 : _d.gvc, (_e = obj.format) === null || _e === void 0 ? void 0 : _e.output, (_f = obj.format) === null || _f === void 0 ? void 0 : _f.ts, ]; }, }; } exports.extraProfile = extraProfile; async function getTokenServerInfo(profile) { const secretInput = (0, uuid_1.v4)(); const tokenServerSecret = new TextEncoder().encode(secretInput); const alg = 'HS256'; const jwt = await new jose.SignJWT({ name: profile, }) .setProtectedHeader({ alg }) .setIssuedAt() .setIssuer('cpln:issuer') .setAudience('cpln:audience') .sign(tokenServerSecret); return { secret: secretInput, code: jwt, }; } //# sourceMappingURL=profile.js.map