@@ -2,10 +2,10 @@ import isInteractive from 'is-interactive'
2
2
import meow from 'meow'
3
3
import ora from 'ora'
4
4
import prompts from 'prompts'
5
+ import terminalLink from 'terminal-link'
5
6
6
- import { ChalkOrMarkdown } from '../../utils/chalk-markdown.js'
7
7
import { AuthError , InputError } from '../../utils/errors.js'
8
- import { setupSdk } from '../../utils/sdk.js'
8
+ import { FREE_API_KEY , setupSdk } from '../../utils/sdk.js'
9
9
import { getSetting , updateSetting } from '../../utils/settings.js'
10
10
11
11
const description = 'Socket API login'
@@ -29,38 +29,96 @@ export const login = {
29
29
importMeta,
30
30
} )
31
31
32
+ /**
33
+ * @param {{aborted: boolean} } state
34
+ */
35
+ const promptAbortHandler = ( state ) => {
36
+ if ( state . aborted ) {
37
+ process . nextTick ( ( ) => process . exit ( 1 ) )
38
+ }
39
+ }
40
+
32
41
if ( cli . input . length ) cli . showHelp ( )
33
42
34
43
if ( ! isInteractive ( ) ) {
35
44
throw new InputError ( 'cannot prompt for credentials in a non-interactive shell' )
36
45
}
37
- const format = new ChalkOrMarkdown ( false )
38
- const { apiKey } = await prompts ( {
46
+ const result = await prompts ( {
39
47
type : 'password' ,
40
48
name : 'apiKey' ,
41
- message : `Enter your ${ format . hyperlink (
49
+ message : `Enter your ${ terminalLink (
42
50
'Socket.dev API key' ,
43
51
'https://docs.socket.dev/docs/api-keys'
44
- ) } `,
52
+ ) } (leave blank for a public key)`,
53
+ onState : promptAbortHandler
45
54
} )
46
55
47
- if ( ! apiKey ) {
48
- ora ( 'API key not updated' ) . warn ( )
49
- return
50
- }
56
+ const apiKey = result . apiKey || FREE_API_KEY
51
57
52
58
const spinner = ora ( 'Verifying API key...' ) . start ( )
53
59
54
- const oldKey = getSetting ( 'apiKey' )
55
- updateSetting ( 'apiKey' , apiKey )
60
+ /** @type {import('@socketsecurity/sdk').SocketSdkReturnType<'getOrganizations'>['data'] } */
61
+ let orgs
62
+
56
63
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' )
61
69
} catch ( e ) {
62
- updateSetting ( 'apiKey' , oldKey )
63
70
spinner . fail ( 'Invalid API key' )
71
+ return
64
72
}
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' } ` )
65
123
}
66
124
}
0 commit comments