@pinpt/cli
Advanced tools
+4
-1
@@ -19,3 +19,3 @@ #!/usr/bin/env node | ||
| logout: { description: 'Logout of Pinpoint on this machine' }, | ||
| signup: { description: 'Signup for Pinpoint' }, | ||
| // signup: { description: 'Signup for Pinpoint' }, | ||
| }; | ||
@@ -26,2 +26,5 @@ | ||
| const [options, args] = getArgs(optionSpec); | ||
| if (args.length === 0 || !commandSpec[args[0]]) { | ||
| options.help = true; | ||
| } | ||
| await run({ args, options, optionSpec, commandSpec, meta: import.meta }); | ||
@@ -28,0 +31,0 @@ } catch (ex) { |
+1
-0
@@ -121,2 +121,3 @@ import prompts from 'prompts'; | ||
| body, | ||
| apiKeyRequired: true, | ||
| onProgress: (progress) => { | ||
@@ -123,0 +124,0 @@ if (p) { |
+2
-3
| import { getSignup } from './signup.mjs'; | ||
| import { saveAPIKey } from './util.mjs'; | ||
| export const login = async ({ args }) => { | ||
| export const login = async ({ args, options }) => { | ||
| if (args && args[0] === 'login') { | ||
| saveAPIKey(); // remove it | ||
| await getSignup('login'); | ||
| await getSignup('login', options.host); | ||
| } | ||
| }; |
+16
-24
@@ -5,3 +5,3 @@ import prompts from 'prompts'; | ||
| import terminalLink from 'terminal-link'; | ||
| import { getAPIKey, saveConfigProps, saveAPIKey, apiRequest, debug, error, tick, configFileName } from './util.mjs'; | ||
| import { getAPIKey, saveAPIKey, apiRequest, error, tick, configFileName, selectUser } from './util.mjs'; | ||
@@ -28,3 +28,3 @@ const getEmailPrompt = () => { | ||
| const handlePromptFlow = async (email, loginToken, userId) => { | ||
| const handlePromptFlow = async (email, loginToken, userId, apihost) => { | ||
| console.log(); | ||
@@ -35,2 +35,3 @@ console.log( | ||
| console.log(); | ||
| let siteIds; | ||
| while (true) { | ||
@@ -51,3 +52,3 @@ const code = await prompts( | ||
| try { | ||
| await apiRequest('Verifying the code', '/user/signup/code', { | ||
| const res = await apiRequest('Verifying the code', '/user/signup/code', { | ||
| body: { email: email, code: code.value }, | ||
@@ -57,2 +58,3 @@ method: 'PUT', | ||
| }); | ||
| siteIds = res.siteIds; | ||
| break; | ||
@@ -78,4 +80,3 @@ } catch (ex) { | ||
| // save it | ||
| saveAPIKey(loginToken, Date.now() + 2.592e9 - 40000); | ||
| saveConfigProps({ userId }); | ||
| saveAPIKey(apihost, userId, loginToken, Date.now() + 2.592e9 - 40000, siteIds, email); | ||
| tick(`We saved your cached credentials at ${configFileName}`); | ||
@@ -110,4 +111,7 @@ console.log(); | ||
| export const selectSite = async () => { | ||
| const { sites } = await apiRequest('Fetching your sites', '/user/sites'); | ||
| export const selectSite = async (apihost) => { | ||
| const userId = await selectUser(apihost); | ||
| const apiKey = getAPIKey(apihost, '', userId); | ||
| const { sites } = await apiRequest('Fetching your sites', '/user/sites', { apiKey, apiKeyRequired: true }); | ||
| if (sites.length === 0) { | ||
@@ -141,15 +145,3 @@ error('No sites found. Please create a site first.', true); | ||
| export const getSignup = async (action) => { | ||
| const apikey = getAPIKey(); | ||
| if (apikey) { | ||
| try { | ||
| await apiRequest('Verifying your credentials', '/auth/ping', { quiet: true, failOnError: false }); | ||
| debug('we already have a valid and unexpired apikey'); | ||
| return; // succeeded | ||
| } catch (ex) { | ||
| saveAPIKey(); // delete it | ||
| // ignore if failed, as that means it's not authorized, invalid, etc | ||
| } | ||
| } | ||
| export const getSignup = async (action, apihost) => { | ||
| let signup = action === 'signup'; | ||
@@ -185,3 +177,3 @@ let login = action === 'login'; | ||
| const { userId, loginToken } = res; | ||
| await handlePromptFlow(email.value, loginToken, userId); | ||
| await handlePromptFlow(email.value, loginToken, userId, apihost); | ||
| } else { | ||
@@ -364,3 +356,3 @@ const signup = { user: {}, site: {}, offline: true }; | ||
| const { userId, loginToken } = res; | ||
| await handlePromptFlow(signup.user.email, loginToken, userId); | ||
| await handlePromptFlow(signup.user.email, loginToken, userId, apihost); | ||
| console.log(); | ||
@@ -372,6 +364,6 @@ console.log(c.bold(c.green('🚀 Your account is activated!'))); | ||
| export const signup = async ({ args }) => { | ||
| export const signup = async ({ args, options }) => { | ||
| if (args && args[0] === 'signup') { | ||
| await getSignup('signup'); | ||
| await getSignup('signup', options.host); | ||
| } | ||
| }; |
+117
-33
@@ -9,2 +9,3 @@ import CFonts from 'cfonts'; | ||
| import vm from 'vm'; | ||
| import prompts from 'prompts'; | ||
| import terminalLink from 'terminal-link'; | ||
@@ -97,2 +98,17 @@ import getstream from 'get-stream'; | ||
| /** | ||
| * structure for config: | ||
| * | ||
| * "apihost": { | ||
| * "userId": { | ||
| * "siteIds": [], | ||
| * "apikey": { | ||
| * "value": "", | ||
| * "expires": "" | ||
| * } | ||
| * } | ||
| * } | ||
| * | ||
| */ | ||
| export const getConfig = () => { | ||
@@ -110,9 +126,28 @@ if (CONFIG) { | ||
| export const saveAPIKey = (value, expires) => { | ||
| export const saveAPIKey = (apihost, userId, value, expires, siteIds, email) => { | ||
| if (!apihost) { | ||
| if (fs.existsSync(configFileName)) { | ||
| fs.unlinkSync(configFileName); | ||
| } | ||
| return; | ||
| } | ||
| const c = getConfig() || {}; | ||
| const _apiconfig = c[apihost] || {}; | ||
| if (value && expires > Date.now()) { | ||
| c.apikey = { value, expires }; | ||
| _apiconfig[userId] = { | ||
| email, | ||
| siteIds, | ||
| apikey: { | ||
| value, | ||
| expires, | ||
| }, | ||
| }; | ||
| } else { | ||
| delete c.apikey; // delete it | ||
| if (userId) { | ||
| delete _apiconfig[userId]; | ||
| } else { | ||
| delete c[apihost]; | ||
| } | ||
| } | ||
| c[apihost] = _apiconfig; | ||
| CONFIG = c; | ||
@@ -123,15 +158,31 @@ fs.writeFileSync(configFileName, JSON.stringify(c, null, 2)); | ||
| export const getConfigProp = (key) => { | ||
| const c = getConfig() || {}; | ||
| return c[key]; | ||
| export const selectUser = async (apihost) => { | ||
| const config = getConfig(); | ||
| if (config) { | ||
| const _apiconfig = config[apihost]; | ||
| if (_apiconfig) { | ||
| const userIds = Object.keys(_apiconfig); | ||
| if (userIds === 1) { | ||
| return userIds[0]; | ||
| } | ||
| const user = await prompts( | ||
| { | ||
| type: 'select', | ||
| name: 'value', | ||
| message: 'Please pick the user you want to use:', | ||
| choices: userIds.map((userId) => ({ title: _apiconfig[userId].email, description: userId })), | ||
| }, | ||
| { | ||
| onCancel: () => { | ||
| process.exit(0); | ||
| }, | ||
| } | ||
| ); | ||
| return userIds[user.value]; | ||
| } | ||
| } | ||
| error('Your login session has expired or you need to login for the first time', true); | ||
| }; | ||
| export const saveConfigProps = (props) => { | ||
| const c = { ...(getConfig() || {}), ...props }; | ||
| CONFIG = c; | ||
| fs.writeFileSync(configFileName, JSON.stringify(c, null, 2)); | ||
| fs.chmodSync(configFileName, '600'); | ||
| }; | ||
| export const getAPIKey = () => { | ||
| export const getAPIKey = (apihost, siteId, _userId) => { | ||
| if (process.env.PINPOINT_API_KEY) { | ||
@@ -141,11 +192,21 @@ return process.env.PINPOINT_API_KEY; | ||
| const config = getConfig(); | ||
| if (config && config.apikey) { | ||
| const { value, expires } = config.apikey; | ||
| const expired = expires <= Date.now(); | ||
| if (!expired) { | ||
| // if it expires in the future, return it | ||
| return value; | ||
| if (config) { | ||
| const _configForHost = config[apihost]; | ||
| if (_configForHost) { | ||
| const userId = | ||
| _userId || | ||
| Object.keys(_configForHost).find( | ||
| (userId) => _configForHost[userId].siteIds && _configForHost[userId].siteIds.includes(siteId) | ||
| ); | ||
| if (userId) { | ||
| const { value, expires = 0 } = _configForHost[userId].apikey || {}; | ||
| const expired = expires <= Date.now(); | ||
| if (!expired) { | ||
| // if it expires in the future, return it | ||
| return value; | ||
| } | ||
| debug('need to remove expired api key'); | ||
| saveAPIKey(apihost, userId); // remove it since expired | ||
| } | ||
| } | ||
| debug('need to remove expired api key'); | ||
| saveAPIKey(); // remove it since expired | ||
| } | ||
@@ -186,2 +247,4 @@ return null; | ||
| const sleep = (timeout) => new Promise((resolve) => setTimeout(resolve, timeout)); | ||
| export const apiRequest = async (help, basepath, params = {}) => { | ||
@@ -193,2 +256,3 @@ const { | ||
| failOnError = true, | ||
| apiKeyRequired = false, | ||
| apiKey, | ||
@@ -203,4 +267,15 @@ checkSuccess, | ||
| } | ||
| const { apihost } = readPinpointConfig(); | ||
| const _apiKey = apiKey || getAPIKey(); | ||
| let _apiKey = apiKey; | ||
| let apihost = _options.host; | ||
| if (apiKeyRequired && !_apiKey) { | ||
| const { apihost: _apihost, siteId } = readPinpointConfig(); | ||
| _apiKey = getAPIKey(apihost, siteId); | ||
| if (!_apiKey) { | ||
| error( | ||
| 'You are not logged in to this site or your login session has expired. Please login again and try again.', | ||
| true | ||
| ); | ||
| } | ||
| apihost = _apihost; | ||
| } | ||
| const headers = {}; | ||
@@ -213,3 +288,3 @@ const [ip, machine] = await Promise.all([ipAddress(), getSystemInfo()]); | ||
| headers['x-pinpoint-machine'] = machine; | ||
| const url = `https://${apihost || _options.host}${basepath}`; | ||
| const url = `https://${apihost}${basepath}`; | ||
| const _method = body ? method : 'GET'; | ||
@@ -246,10 +321,19 @@ const isStream = body instanceof FormData; | ||
| } else { | ||
| g = got(url, { | ||
| headers, | ||
| method: _method, | ||
| responseType: 'json', | ||
| body: _body, | ||
| throwHttpErrors: false, | ||
| }); | ||
| p = g; | ||
| let count = 0; | ||
| while (true) { | ||
| count++; | ||
| g = got(url, { | ||
| headers, | ||
| method: _method, | ||
| responseType: 'json', | ||
| body: _body, | ||
| throwHttpErrors: false, | ||
| }); | ||
| p = await g; | ||
| if (p.statusCode === 502) { | ||
| await sleep(count * Math.max(150, Math.random() * 500)); // exponential backoff | ||
| continue; | ||
| } | ||
| break; | ||
| } | ||
| } | ||
@@ -256,0 +340,0 @@ const res = await p; |
+1
-1
| { | ||
| "name": "@pinpt/cli", | ||
| "version": "0.0.9", | ||
| "version": "1.0.0", | ||
| "description": "Pinpoint CLI", | ||
@@ -5,0 +5,0 @@ "bin": { |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
29564
6.51%1048
8.04%0
-100%