@controlplane/cli
Version:
Control Plane Corporation CLI
571 lines • 21.6 kB
JavaScript
"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