Skip to content

Commit ae2f58c

Browse files
authored
Merge pull request #46 from SocketDev/upgraded-cli-login
Upgraded CLI login
2 parents fca96a3 + 665547b commit ae2f58c

File tree

10 files changed

+270
-84
lines changed

10 files changed

+270
-84
lines changed

lib/commands/info/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { InputError } from '../../utils/errors.js'
1111
import { getSeverityCount, formatSeverityCount } from '../../utils/format-issues.js'
1212
import { printFlagList } from '../../utils/formatting.js'
1313
import { objectSome } from '../../utils/misc.js'
14-
import { setupSdk } from '../../utils/sdk.js'
14+
import { FREE_API_KEY, getDefaultKey, setupSdk } from '../../utils/sdk.js'
1515

1616
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
1717
export const info = {
@@ -124,7 +124,7 @@ function setupCommand (name, description, argv, importMeta) {
124124
* @returns {Promise<void|PackageData>}
125125
*/
126126
async function fetchPackageData (pkgName, pkgVersion, { includeAllIssues, strict }) {
127-
const socketSdk = await setupSdk()
127+
const socketSdk = await setupSdk(getDefaultKey() || FREE_API_KEY)
128128
const spinner = ora(`Looking up data for version ${pkgVersion} of ${pkgName}`).start()
129129
const result = await handleApiCall(socketSdk.getIssuesByNPMPackage(pkgName, pkgVersion), 'looking up package')
130130

lib/commands/login/index.js

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import isInteractive from 'is-interactive'
22
import meow from 'meow'
33
import ora from 'ora'
44
import prompts from 'prompts'
5+
import terminalLink from 'terminal-link'
56

6-
import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js'
77
import { AuthError, InputError } from '../../utils/errors.js'
8-
import { setupSdk } from '../../utils/sdk.js'
8+
import { FREE_API_KEY, setupSdk } from '../../utils/sdk.js'
99
import { getSetting, updateSetting } from '../../utils/settings.js'
1010

1111
const description = 'Socket API login'
@@ -29,38 +29,96 @@ export const login = {
2929
importMeta,
3030
})
3131

32+
/**
33+
* @param {{aborted: boolean}} state
34+
*/
35+
const promptAbortHandler = (state) => {
36+
if (state.aborted) {
37+
process.nextTick(() => process.exit(1))
38+
}
39+
}
40+
3241
if (cli.input.length) cli.showHelp()
3342

3443
if (!isInteractive()) {
3544
throw new InputError('cannot prompt for credentials in a non-interactive shell')
3645
}
37-
const format = new ChalkOrMarkdown(false)
38-
const { apiKey } = await prompts({
46+
const result = await prompts({
3947
type: 'password',
4048
name: 'apiKey',
41-
message: `Enter your ${format.hyperlink(
49+
message: `Enter your ${terminalLink(
4250
'Socket.dev API key',
4351
'https://docs.socket.dev/docs/api-keys'
44-
)}`,
52+
)} (leave blank for a public key)`,
53+
onState: promptAbortHandler
4554
})
4655

47-
if (!apiKey) {
48-
ora('API key not updated').warn()
49-
return
50-
}
56+
const apiKey = result.apiKey || FREE_API_KEY
5157

5258
const spinner = ora('Verifying API key...').start()
5359

54-
const oldKey = getSetting('apiKey')
55-
updateSetting('apiKey', apiKey)
60+
/** @type {import('@socketsecurity/sdk').SocketSdkReturnType<'getOrganizations'>['data']} */
61+
let orgs
62+
5663
try {
57-
const sdk = await setupSdk()
58-
const quota = await sdk.getQuota()
59-
if (!quota.success) throw new AuthError()
60-
spinner.succeed(`API key ${oldKey ? 'updated' : 'set'}`)
64+
const sdk = await setupSdk(apiKey)
65+
const result = await sdk.getOrganizations()
66+
if (!result.success) throw new AuthError()
67+
orgs = result.data
68+
spinner.succeed('API key verified\n')
6169
} catch (e) {
62-
updateSetting('apiKey', oldKey)
6370
spinner.fail('Invalid API key')
71+
return
6472
}
73+
74+
/**
75+
* @template T
76+
* @param {T | null | undefined} value
77+
* @returns {value is T}
78+
*/
79+
const nonNullish = value => value != null
80+
81+
/** @type {prompts.Choice[]} */
82+
const enforcedChoices = Object.values(orgs.organizations)
83+
.filter(nonNullish)
84+
.filter(org => org.plan === 'enterprise')
85+
.map(org => ({
86+
title: org.name,
87+
value: org.id
88+
}))
89+
90+
/** @type {string[]} */
91+
let enforcedOrgs = []
92+
93+
if (enforcedChoices.length > 1) {
94+
const { id } = await prompts({
95+
type: 'select',
96+
name: 'id',
97+
hint: '\n Pick "None" if this is a personal device',
98+
message: 'Which organization\'s policies should Socket enforce system-wide?',
99+
choices: enforcedChoices.concat({
100+
title: 'None',
101+
value: null
102+
}),
103+
onState: promptAbortHandler
104+
})
105+
if (id) enforcedOrgs = [id]
106+
} else if (enforcedChoices.length) {
107+
const { confirmOrg } = await prompts({
108+
type: 'confirm',
109+
name: 'confirmOrg',
110+
message: `Should Socket enforce ${enforcedChoices[0]?.title}'s security policies system-wide?`,
111+
initial: true,
112+
onState: promptAbortHandler
113+
})
114+
if (confirmOrg) {
115+
enforcedOrgs = [enforcedChoices[0]?.value]
116+
}
117+
}
118+
// MUST DO all updateSetting ON SAME TICK TO AVOID PARTIAL WRITE
119+
updateSetting('enforcedOrgs', enforcedOrgs)
120+
const oldKey = getSetting('apiKey')
121+
updateSetting('apiKey', apiKey)
122+
spinner.succeed(`API credentials ${oldKey ? 'updated' : 'set'}`)
65123
}
66124
}

lib/commands/logout/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const logout = {
2727
if (cli.input.length) cli.showHelp()
2828

2929
updateSetting('apiKey', null)
30+
updateSetting('enforcedOrgs', null)
3031
ora('Successfully logged out').succeed()
3132
}
3233
}

lib/shadow/link.cjs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,34 @@ const path = require('path')
55
const which = require('which')
66

77
if (process.platform === 'win32') {
8-
console.error('Socket npm and socket npx wrapper Windows suppport is limited to WSL at this time.')
8+
console.error('Socket dependency manager Windows suppport is limited to WSL at this time.')
99
process.exit(1)
1010
}
1111

1212
/**
1313
* @param {string} realDirname path to shadow/bin
1414
* @param {'npm' | 'npx'} binname
15-
* @returns {string} path to npm provided cli / npx bin
15+
* @returns {string} path to original bin
1616
*/
1717
function installLinks (realDirname, binname) {
18-
const realNpmShadowBinDir = realDirname
19-
// find npm being shadowed by this process
20-
const npms = which.sync(binname, {
18+
const realShadowBinDir = realDirname
19+
// find package manager being shadowed by this process
20+
const bins = which.sync(binname, {
2121
all: true
2222
})
2323
let shadowIndex = -1
24-
const npmpath = npms.find((npmPath, i) => {
25-
const isShadow = realpathSync(path.dirname(npmPath)) === realNpmShadowBinDir
24+
const binpath = bins.find((binPath, i) => {
25+
const isShadow = realpathSync(path.dirname(binPath)) === realShadowBinDir
2626
if (isShadow) {
2727
shadowIndex = i
2828
}
2929
return !isShadow
3030
})
31-
if (npmpath && process.platform === 'win32') {
32-
return npmpath
31+
if (binpath && process.platform === 'win32') {
32+
return binpath
3333
}
34-
if (!npmpath) {
35-
console.error('Socket unable to locate npm ensure it is available in the PATH environment variable')
34+
if (!binpath) {
35+
console.error(`Socket unable to locate ${binname}; ensure it is available in the PATH environment variable`)
3636
process.exit(127)
3737
}
3838
if (shadowIndex === -1) {

0 commit comments

Comments
 (0)