Skip to content

Commit af7d187

Browse files
committed
cli login/logout
1 parent e6280b3 commit af7d187

File tree

6 files changed

+158
-7
lines changed

6 files changed

+158
-7
lines changed

lib/commands/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ export * from './info/index.js'
22
export * from './report/index.js'
33
export * from './npm/index.js'
44
export * from './npx/index.js'
5+
export * from './login/index.js'
6+
export * from './logout/index.js'

lib/commands/login/index.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import isInteractive from 'is-interactive'
2+
import meow from 'meow'
3+
import ora from 'ora'
4+
import prompts from 'prompts'
5+
6+
import { AuthError, InputError } from '../../utils/errors.js'
7+
import { setupSdk } from '../../utils/sdk.js'
8+
import { getSetting, updateSetting } from '../../utils/settings.js'
9+
10+
const description = 'Socket API login'
11+
12+
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
13+
export const login = {
14+
description,
15+
run: async (argv, importMeta, { parentName }) => {
16+
const name = parentName + ' login'
17+
const cli = meow(`
18+
Usage
19+
$ ${name}
20+
21+
Logs into the Socket API by prompting for an API key
22+
23+
Examples
24+
$ ${name}
25+
`, {
26+
argv,
27+
description,
28+
importMeta,
29+
})
30+
31+
if (cli.input.length) cli.showHelp()
32+
33+
if (!isInteractive()) {
34+
throw new InputError('cannot prompt for credentials in a non-interactive shell')
35+
}
36+
const { apiKey } = await prompts({
37+
type: 'password',
38+
name: 'apiKey',
39+
message: 'Enter your Socket.dev API key',
40+
})
41+
42+
if (!apiKey) {
43+
ora('API key not updated').warn()
44+
return
45+
}
46+
47+
const spinner = ora('Verifying API key...').start()
48+
49+
const oldKey = getSetting('apiKey')
50+
updateSetting('apiKey', apiKey)
51+
try {
52+
const sdk = await setupSdk()
53+
const quota = await sdk.getQuota()
54+
if (!quota.success) throw new AuthError()
55+
spinner.succeed(`API key ${oldKey ? 'updated' : 'set'}`)
56+
} catch (e) {
57+
updateSetting('apiKey', oldKey)
58+
spinner.fail('Invalid API key')
59+
}
60+
}
61+
}

lib/commands/logout/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import meow from 'meow'
2+
import ora from 'ora'
3+
4+
import { updateSetting } from '../../utils/settings.js'
5+
6+
const description = 'Socket API logout'
7+
8+
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
9+
export const logout = {
10+
description,
11+
run: async (argv, importMeta, { parentName }) => {
12+
const name = parentName + ' logout'
13+
const cli = meow(`
14+
Usage
15+
$ ${name}
16+
17+
Logs out of the Socket API and clears all Socket credentials from disk
18+
19+
Examples
20+
$ ${name}
21+
`, {
22+
argv,
23+
description,
24+
importMeta,
25+
})
26+
27+
if (cli.input.length) cli.showHelp()
28+
29+
updateSetting('apiKey', null)
30+
ora('Successfully logged out').succeed()
31+
}
32+
}

lib/utils/sdk.js

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,18 @@ import isInteractive from 'is-interactive'
77
import prompts from 'prompts'
88

99
import { AuthError } from './errors.js'
10+
import { getSetting } from './settings.js'
1011

1112
/**
12-
* The API key should be stored globally for the duration of the CLI execution
13+
* This API key should be stored globally for the duration of the CLI execution
1314
*
1415
* @type {string | undefined}
1516
*/
16-
let apiKey
17+
let sessionAPIKey
1718

1819
/** @returns {Promise<import('@socketsecurity/sdk').SocketSdk>} */
1920
export async function setupSdk () {
20-
if (!apiKey) {
21-
apiKey = process.env['SOCKET_SECURITY_API_KEY']
22-
}
21+
let apiKey = getSetting('apiKey') || process.env['SOCKET_SECURITY_API_KEY'] || sessionAPIKey
2322

2423
if (!apiKey && isInteractive()) {
2524
const input = await prompts({
@@ -28,7 +27,7 @@ export async function setupSdk () {
2827
message: 'Enter your Socket.dev API key',
2928
})
3029

31-
apiKey = input.apiKey
30+
apiKey = sessionAPIKey = input.apiKey
3231
}
3332

3433
if (!apiKey) {

lib/utils/settings.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as fs from 'fs'
2+
import * as os from 'os'
3+
import * as path from 'path'
4+
5+
// @ts-ignore no types for ascii85
6+
import ascii85 from 'ascii85'
7+
8+
let dataHome = process.platform === 'win32'
9+
? process.env['LOCALAPPDATA']
10+
: process.env['XDG_DATA_HOME']
11+
12+
if (!dataHome) {
13+
if (process.platform === 'win32') throw new Error('missing %LOCALAPPDATA%')
14+
const home = os.homedir()
15+
dataHome = path.join(home, ...(process.platform === 'darwin'
16+
? ['Library', 'Application Support']
17+
: ['.local', 'share']
18+
))
19+
}
20+
21+
const settingsPath = path.join(dataHome, 'socket', 'settings')
22+
23+
/** @type {any} */
24+
let settings = {}
25+
26+
if (fs.existsSync(settingsPath)) {
27+
const raw = fs.readFileSync(settingsPath)
28+
try {
29+
settings = JSON.parse(ascii85.decode(raw).toString())
30+
} catch (e) {
31+
// failed decode - don't load
32+
}
33+
} else {
34+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true })
35+
}
36+
37+
/**
38+
* @param {string} key
39+
* @returns {any}
40+
*/
41+
export function getSetting (key) {
42+
return settings[key]
43+
}
44+
45+
/**
46+
* @param {string} key
47+
* @param {any} value
48+
* @returns {void}
49+
*/
50+
export function updateSetting (key, value) {
51+
settings[key] = value
52+
fs.writeFileSync(
53+
settingsPath,
54+
ascii85.encode(JSON.stringify(settings))
55+
)
56+
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"test": "run-s check test:*"
4444
},
4545
"devDependencies": {
46-
"@socketsecurity/eslint-config": "^2.0.0",
46+
"@socketsecurity/eslint-config": "^3.0.1",
4747
"@tsconfig/node14": "^1.0.3",
4848
"@types/chai": "^4.3.3",
4949
"@types/chai-as-promised": "^7.1.5",
@@ -85,6 +85,7 @@
8585
"@apideck/better-ajv-errors": "^0.3.6",
8686
"@socketsecurity/config": "^2.0.0",
8787
"@socketsecurity/sdk": "^0.5.4",
88+
"ascii85": "^1.0.2",
8889
"chalk": "^5.1.2",
8990
"globby": "^13.1.3",
9091
"hpagent": "^1.2.0",

0 commit comments

Comments
 (0)