Skip to content

Commit e95af7f

Browse files
authored
Polish the config/token override stuff (#407)
* Polish the config/token override stuff * More polish * Also add read-only warning to logout
1 parent e4fa88f commit e95af7f

File tree

12 files changed

+402
-26
lines changed

12 files changed

+402
-26
lines changed

src/commands/config/cmd-config-get.test.ts

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,224 @@ describe('socket config get', async () => {
104104
expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
105105
}
106106
)
107+
108+
describe('env vars', () => {
109+
describe('token', () => {
110+
cmdit(
111+
['config', 'get', 'apiToken', '--config', '{"apiToken":null}'],
112+
'should return undefined when token not set in config',
113+
async cmd => {
114+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd, {})
115+
expect(stdout).toMatchInlineSnapshot(
116+
`
117+
"apiToken: null
118+
119+
Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag."
120+
`
121+
)
122+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
123+
"
124+
_____ _ _ /---------------
125+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
126+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
127+
|_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: <redacted>"
128+
`)
129+
130+
expect(stdout.includes('apiToken: null')).toBe(true)
131+
}
132+
)
133+
134+
cmdit(
135+
['config', 'get', 'apiToken', '--config', '{"apiToken":null}'],
136+
'should return the env var token when set',
137+
async cmd => {
138+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd, {
139+
SOCKET_SECURITY_API_TOKEN: 'abc'
140+
})
141+
expect(stdout).toMatchInlineSnapshot(
142+
`
143+
"apiToken: abc
144+
145+
Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag."
146+
`
147+
)
148+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
149+
"
150+
_____ _ _ /---------------
151+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
152+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
153+
|_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: <redacted>"
154+
`)
155+
156+
expect(stdout.includes('apiToken: abc')).toBe(true)
157+
}
158+
)
159+
160+
// Migrate this away...?
161+
cmdit(
162+
['config', 'get', 'apiToken', '--config', '{"apiToken":null}'],
163+
'should backwards compat support api key as well env var',
164+
async cmd => {
165+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd, {
166+
SOCKET_SECURITY_API_KEY: 'abc'
167+
})
168+
expect(stdout).toMatchInlineSnapshot(
169+
`
170+
"apiToken: abc
171+
172+
Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag."
173+
`
174+
)
175+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
176+
"
177+
_____ _ _ /---------------
178+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
179+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
180+
|_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: <redacted>"
181+
`)
182+
183+
expect(stdout.includes('apiToken: abc')).toBe(true)
184+
}
185+
)
186+
187+
cmdit(
188+
['config', 'get', 'apiToken', '--config', '{"apiToken":null}'],
189+
'should be nice and support cli prefixed env var for token as well',
190+
async cmd => {
191+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd, {
192+
SOCKET_CLI_API_TOKEN: 'abc'
193+
})
194+
expect(stdout).toMatchInlineSnapshot(
195+
`
196+
"apiToken: abc
197+
198+
Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag."
199+
`
200+
)
201+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
202+
"
203+
_____ _ _ /---------------
204+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
205+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
206+
|_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: <redacted>"
207+
`)
208+
209+
expect(stdout.includes('apiToken: abc')).toBe(true)
210+
}
211+
)
212+
213+
// Migrate this away...?
214+
cmdit(
215+
['config', 'get', 'apiToken', '--config', '{"apiToken":null}'],
216+
'should be very nice and support cli prefixed env var for key as well since it is an easy mistake to make',
217+
async cmd => {
218+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd, {
219+
SOCKET_CLI_API_KEY: 'abc'
220+
})
221+
expect(stdout).toMatchInlineSnapshot(
222+
`
223+
"apiToken: abc
224+
225+
Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag."
226+
`
227+
)
228+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
229+
"
230+
_____ _ _ /---------------
231+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
232+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
233+
|_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: <redacted>"
234+
`)
235+
236+
expect(stdout.includes('apiToken: abc')).toBe(true)
237+
}
238+
)
239+
240+
cmdit(
241+
[
242+
'config',
243+
'get',
244+
'apiToken',
245+
'--config',
246+
'{"apiToken":"ignoremebecausetheenvvarshouldbemoreimportant"}'
247+
],
248+
'should use the env var token when the config override also has a token set',
249+
async cmd => {
250+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd, {
251+
SOCKET_CLI_API_KEY: 'abc'
252+
})
253+
expect(stdout).toMatchInlineSnapshot(
254+
`
255+
"apiToken: abc
256+
257+
Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag."
258+
`
259+
)
260+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
261+
"
262+
_____ _ _ /---------------
263+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
264+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
265+
|_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: <redacted>"
266+
`)
267+
268+
expect(stdout.includes('apiToken: abc')).toBe(true)
269+
}
270+
)
271+
272+
cmdit(
273+
[
274+
'config',
275+
'get',
276+
'apiToken',
277+
'--config',
278+
'{"apiToken":"pickmepickme"}'
279+
],
280+
'should use the config override when there is no env var',
281+
async cmd => {
282+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd, {})
283+
expect(stdout).toMatchInlineSnapshot(
284+
`
285+
"apiToken: pickmepickme
286+
287+
Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag."
288+
`
289+
)
290+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
291+
"
292+
_____ _ _ /---------------
293+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
294+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
295+
|_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: <redacted>"
296+
`)
297+
298+
expect(stdout.includes('apiToken: pickmepickme')).toBe(true)
299+
}
300+
)
301+
302+
cmdit(
303+
['config', 'get', 'apiToken', '--config', '{}'],
304+
'should yield no token when override has none',
305+
async cmd => {
306+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd, {})
307+
expect(stdout).toMatchInlineSnapshot(
308+
`
309+
"apiToken: undefined
310+
311+
Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag."
312+
`
313+
)
314+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
315+
"
316+
_____ _ _ /---------------
317+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
318+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
319+
|_____|___|___|_,_|___|_|.dev | Command: \`socket config get\`, cwd: <redacted>"
320+
`)
321+
322+
expect(stdout.includes('apiToken: undefined')).toBe(true)
323+
}
324+
)
325+
})
326+
})
107327
})

src/commands/config/cmd-config.test.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'node:path'
22

3-
import { describe, expect } from 'vitest'
3+
import { describe, expect, it } from 'vitest'
44

55
import constants from '../../../dist/constants.js'
66
import { cmdit, invokeNpm } from '../../../test/utils'
@@ -39,12 +39,12 @@ describe('socket config', async () => {
3939
`
4040
)
4141
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
42-
"
43-
_____ _ _ /---------------
44-
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
45-
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
46-
|_____|___|___|_,_|___|_|.dev | Command: \`socket config\`, cwd: <redacted>"
47-
`)
42+
"
43+
_____ _ _ /---------------
44+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
45+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
46+
|_____|___|___|_,_|___|_|.dev | Command: \`socket config\`, cwd: <redacted>"
47+
`)
4848

4949
expect(code, 'help should exit with code 2').toBe(2)
5050
expect(stderr, 'banner includes base command').toContain(
@@ -72,4 +72,27 @@ describe('socket config', async () => {
7272
expect(code, 'dry-run should exit with code 0 if input ok').toBe(0)
7373
}
7474
)
75+
76+
describe('config override', () => {
77+
cmdit(
78+
['config', 'get', 'apiToken', '--config', '{apiToken:invalidjson}'],
79+
'should print nice error when config override cannot be parsed',
80+
async cmd => {
81+
const { code, stderr, stdout } = await invokeNpm(entryPath, cmd, {})
82+
expect(stdout).toMatchInlineSnapshot(`""`)
83+
expect(`\n ${stderr}`).toMatchInlineSnapshot(`
84+
"
85+
_____ _ _ /---------------
86+
| __|___ ___| |_ ___| |_ | Socket.dev CLI ver <redacted>
87+
|__ | . | _| '_| -_| _| | Node: <redacted>, API token set: <redacted>
88+
|_____|___|___|_,_|___|_|.dev | Command: \`socket\`, cwd: <redacted>
89+
90+
\\x1b[31m\\xd7\\x1b[39m Could not JSON parse the config override. Make sure it's a proper JSON object (double-quoted keys and strings, no unquoted \`undefined\`) and try again."
91+
`)
92+
93+
expect(stderr.includes('Could not JSON parse')).toBe(true)
94+
expect(code, 'bad config input should exit with code 2 ').toBe(2)
95+
}
96+
)
97+
})
7598
})

src/commands/config/handle-config-get.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { outputConfigGet } from './output-config-get'
2-
import { getConfigValue } from '../../utils/config'
2+
import { getConfigValue, isReadOnlyConfig } from '../../utils/config'
33

44
import type { LocalConfig } from '../../utils/config'
55

@@ -11,6 +11,7 @@ export async function handleConfigGet({
1111
outputKind: 'json' | 'markdown' | 'text'
1212
}) {
1313
const value = getConfigValue(key)
14+
const readOnly = isReadOnlyConfig()
1415

15-
await outputConfigGet(key, value, outputKind)
16+
await outputConfigGet(key, value, readOnly, outputKind)
1617
}

src/commands/config/handle-config-set.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { outputConfigSet } from './output-config-set'
2-
import { updateConfigValue } from '../../utils/config'
2+
import { isReadOnlyConfig, updateConfigValue } from '../../utils/config'
33

44
import type { LocalConfig } from '../../utils/config'
55

@@ -13,5 +13,7 @@ export async function handleConfigSet({
1313
value: string
1414
}) {
1515
updateConfigValue(key, value)
16-
await outputConfigSet(key, value, outputKind)
16+
const readOnly = isReadOnlyConfig()
17+
18+
await outputConfigSet(key, value, readOnly, outputKind)
1719
}

src/commands/config/output-config-get.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,30 @@ import { LocalConfig } from '../../utils/config'
55
export async function outputConfigGet(
66
key: keyof LocalConfig,
77
value: unknown,
8+
readOnly: boolean, // Is config in read-only mode? (Overrides applied)
89
outputKind: 'json' | 'markdown' | 'text'
910
) {
1011
if (outputKind === 'json') {
11-
logger.log(JSON.stringify({ success: true, result: { key, value } }))
12+
logger.log(
13+
JSON.stringify({ success: true, result: { key, value }, readOnly })
14+
)
1215
} else if (outputKind === 'markdown') {
1316
logger.log(`# Config Value`)
1417
logger.log('')
1518
logger.log(`Config key '${key}' has value '${value}`)
19+
if (readOnly) {
20+
logger.log('')
21+
logger.log(
22+
'Note: the config is in read-only mode, meaning at least one key was temporarily\n overridden from an env var or command flag.'
23+
)
24+
}
1625
} else {
1726
logger.log(`${key}: ${value}`)
27+
if (readOnly) {
28+
logger.log('')
29+
logger.log(
30+
'Note: the config is in read-only mode, meaning at least one key was temporarily overridden from an env var or command flag.'
31+
)
32+
}
1833
}
1934
}

src/commands/config/output-config-list.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { logger } from '@socketsecurity/registry/lib/logger'
22

33
import {
44
getConfigValue,
5+
isReadOnlyConfig,
56
sensitiveConfigKeys,
67
supportedConfigKeys
78
} from '../../utils/config'
@@ -13,6 +14,7 @@ export async function outputConfigList({
1314
full: boolean
1415
outputKind: 'json' | 'markdown' | 'text'
1516
}) {
17+
const readOnly = isReadOnlyConfig()
1618
if (outputKind === 'json') {
1719
const obj: Record<string, unknown> = {}
1820
for (const key of supportedConfigKeys.keys()) {
@@ -29,7 +31,8 @@ export async function outputConfigList({
2931
{
3032
success: true,
3133
full,
32-
config: obj
34+
config: obj,
35+
readOnly
3336
},
3437
null,
3538
2
@@ -56,5 +59,11 @@ export async function outputConfigList({
5659
)
5760
}
5861
}
62+
if (readOnly) {
63+
logger.log('')
64+
logger.log(
65+
'Note: the config is in read-only mode, meaning at least one key was temporarily\n overridden from an env var or command flag.'
66+
)
67+
}
5968
}
6069
}

0 commit comments

Comments
 (0)