Skip to content

Commit 2e3c389

Browse files
authored
Merge pull request #78 from SocketDev/12-factor-fix
Fixes for offline / npm
2 parents 03ff3cc + c704f15 commit 2e3c389

File tree

10 files changed

+196
-63
lines changed

10 files changed

+196
-63
lines changed

lib/commands/login/index.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import prompts from 'prompts'
55
import terminalLink from 'terminal-link'
66

77
import { AuthError, InputError } from '../../utils/errors.js'
8+
import { prepareFlags } from '../../utils/flags.js'
9+
import { printFlagList } from '../../utils/formatting.js'
810
import { FREE_API_KEY, setupSdk } from '../../utils/sdk.js'
911
import { getSetting, updateSetting } from '../../utils/settings.js'
1012

@@ -14,19 +16,36 @@ const description = 'Socket API login'
1416
export const login = {
1517
description,
1618
run: async (argv, importMeta, { parentName }) => {
19+
const flags = prepareFlags({
20+
apiBaseUrl: {
21+
type: 'string',
22+
description: 'API server to connect to for login',
23+
},
24+
apiProxy: {
25+
type: 'string',
26+
description: 'Proxy to use when making connection to API server'
27+
}
28+
})
1729
const name = parentName + ' login'
1830
const cli = meow(`
1931
Usage
2032
$ ${name}
2133
2234
Logs into the Socket API by prompting for an API key
2335
36+
Options
37+
${printFlagList({
38+
'api-base-url': flags.apiBaseUrl.description,
39+
'api-proxy': flags.apiProxy.description
40+
}, 8)}
41+
2442
Examples
2543
$ ${name}
2644
`, {
2745
argv,
2846
description,
2947
importMeta,
48+
flags
3049
})
3150

3251
/**
@@ -58,13 +77,27 @@ export const login = {
5877

5978
const apiKey = result.apiKey || FREE_API_KEY
6079

80+
/**
81+
* @type {string | null | undefined}
82+
*/
83+
let apiBaseUrl = cli.flags.apiBaseUrl
84+
apiBaseUrl ??= getSetting('apiBaseUrl') ??
85+
undefined
86+
87+
/**
88+
* @type {string | null | undefined}
89+
*/
90+
let apiProxy = cli.flags.apiProxy
91+
apiProxy ??= getSetting('apiProxy') ??
92+
undefined
93+
6194
const spinner = ora('Verifying API key...').start()
6295

6396
/** @type {import('@socketsecurity/sdk').SocketSdkReturnType<'getOrganizations'>['data']} */
6497
let orgs
6598

6699
try {
67-
const sdk = await setupSdk(apiKey)
100+
const sdk = await setupSdk(apiKey, apiBaseUrl, apiProxy)
68101
const result = await sdk.getOrganizations()
69102
if (!result.success) throw new AuthError()
70103
orgs = result.data
@@ -131,6 +164,7 @@ export const login = {
131164
updateSetting('enforcedOrgs', enforcedOrgs)
132165
const oldKey = getSetting('apiKey')
133166
updateSetting('apiKey', apiKey)
167+
updateSetting('apiBaseUrl', apiBaseUrl)
134168
spinner.succeed(`API credentials ${oldKey ? 'updated' : 'set'}`)
135169
}
136170
}

lib/commands/logout/index.js

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

2929
updateSetting('apiKey', null)
30+
updateSetting('apiBaseUrl', null)
31+
updateSetting('apiProxy', null)
3032
updateSetting('enforcedOrgs', null)
3133
ora('Successfully logged out').succeed()
3234
}

lib/commands/report/create.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ async function setupCommand (name, description, argv, importMeta) {
179179
}
180180
})
181181

182-
// TODO: setupSdk(getDefaultKey() || FREE_API_KEY)
183182
const socketSdk = await setupSdk()
184183
const supportedFiles = await socketSdk.getReportSupportedFiles()
185184
.then(res => {

lib/shadow/npm-injection.cjs

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -40,34 +40,50 @@ try {
4040
*/
4141

4242
const pubTokenPromise = sdkPromise.then(({ getDefaultKey, FREE_API_KEY }) => getDefaultKey() || FREE_API_KEY)
43-
const apiKeySettingsPromise = sdkPromise.then(async ({ setupSdk }) => {
44-
const sdk = await setupSdk(await pubTokenPromise)
45-
const orgResult = await sdk.getOrganizations()
46-
if (!orgResult.success) {
47-
throw new Error('Failed to fetch Socket organization info: ' + orgResult.error.message)
48-
}
49-
/**
50-
* @type {(Exclude<typeof orgResult.data.organizations[string], undefined>)[]}
51-
*/
52-
const orgs = []
53-
for (const org of Object.values(orgResult.data.organizations)) {
54-
if (org) {
55-
orgs.push(org)
43+
const apiKeySettingsInit = sdkPromise.then(async ({ setupSdk }) => {
44+
try {
45+
const sdk = await setupSdk(await pubTokenPromise)
46+
const orgResult = await sdk.getOrganizations()
47+
if (!orgResult.success) {
48+
throw new Error('Failed to fetch Socket organization info: ' + orgResult.error.message)
49+
}
50+
/**
51+
* @type {(Exclude<typeof orgResult.data.organizations[string], undefined>)[]}
52+
*/
53+
const orgs = []
54+
for (const org of Object.values(orgResult.data.organizations)) {
55+
if (org) {
56+
orgs.push(org)
57+
}
58+
}
59+
const result = await sdk.postSettings(orgs.map(org => {
60+
return {
61+
organization: org.id
62+
}
63+
}))
64+
if (!result.success) {
65+
throw new Error('Failed to fetch API key settings: ' + result.error.message)
5666
}
57-
}
58-
const result = await sdk.postSettings(orgs.map(org => {
5967
return {
60-
organization: org.id
68+
orgs,
69+
settings: result.data
6170
}
62-
}))
63-
if (!result.success) {
64-
throw new Error('Failed to fetch API key settings: ' + result.error.message)
65-
}
66-
return {
67-
orgs,
68-
settings: result.data
71+
} catch (e) {
72+
if (e && typeof e === 'object' && 'cause' in e) {
73+
const cause = e.cause
74+
if (isErrnoException(cause)) {
75+
if (cause.code === 'ENOTFOUND' || cause.code === 'ECONNREFUSED') {
76+
throw new Error('Unable to connect to socket.dev, ensure internet connectivity before retrying', {
77+
cause: e
78+
})
79+
}
80+
}
81+
}
82+
throw e
6983
}
7084
})
85+
// mark apiKeySettingsInit as handled
86+
apiKeySettingsInit.catch(() => {})
7187

7288
/**
7389
*
@@ -78,42 +94,43 @@ async function findSocketYML () {
7894
const fs = require('fs/promises')
7995
while (dir !== prevDir) {
8096
const ymlPath = path.join(dir, 'socket.yml')
97+
const yml = fs.readFile(ymlPath, 'utf-8')
8198
// mark as handled
82-
const yml = fs.readFile(ymlPath, 'utf-8').catch(() => {})
99+
yml.catch(() => {})
83100
const yamlPath = path.join(dir, 'socket.yaml')
101+
const yaml = fs.readFile(yamlPath, 'utf-8')
84102
// mark as handled
85-
const yaml = fs.readFile(yamlPath, 'utf-8').catch(() => {})
86-
try {
87-
const txt = await yml
88-
if (txt != null) {
89-
return {
90-
path: ymlPath,
91-
parsed: config.parseSocketConfig(txt)
92-
}
93-
}
94-
} catch (e) {
103+
yaml.catch(() => {})
104+
/**
105+
* @param {unknown} e
106+
* @returns {boolean}
107+
*/
108+
function checkFileFoundError (e) {
95109
if (isErrnoException(e)) {
96110
if (e.code !== 'ENOENT' && e.code !== 'EISDIR') {
97111
throw e
98112
}
99-
} else {
113+
return false
114+
}
115+
return true
116+
}
117+
try {
118+
return {
119+
path: ymlPath,
120+
parsed: config.parseSocketConfig(await yml)
121+
}
122+
} catch (e) {
123+
if (checkFileFoundError(e)) {
100124
throw new Error('Found file but was unable to parse ' + ymlPath)
101125
}
102126
}
103127
try {
104-
const txt = await yaml
105-
if (txt != null) {
106-
return {
107-
path: yamlPath,
108-
parsed: config.parseSocketConfig(txt)
109-
}
128+
return {
129+
path: ymlPath,
130+
parsed: config.parseSocketConfig(await yaml)
110131
}
111132
} catch (e) {
112-
if (isErrnoException(e)) {
113-
if (e.code !== 'ENOENT' && e.code !== 'EISDIR') {
114-
throw e
115-
}
116-
} else {
133+
if (checkFileFoundError(e)) {
117134
throw new Error('Found file but was unable to parse ' + yamlPath)
118135
}
119136
}
@@ -124,11 +141,12 @@ async function findSocketYML () {
124141
}
125142

126143
/**
127-
* @type {Promise<ReturnType<import('../utils/issue-rules.cjs')['createIssueUXLookup']>>}
144+
* @type {Promise<ReturnType<import('../utils/issue-rules.cjs')['createIssueUXLookup']> | undefined>}
128145
*/
129-
const uxLookupPromise = settingsPromise.then(async ({ getSetting }) => {
146+
const uxLookupInit = settingsPromise.then(async ({ getSetting }) => {
130147
const enforcedOrgs = getSetting('enforcedOrgs') ?? []
131-
const { orgs, settings } = await apiKeySettingsPromise
148+
const remoteSettings = await apiKeySettingsInit
149+
const { orgs, settings } = remoteSettings
132150

133151
// remove any organizations not being enforced
134152
for (const [i, org] of orgs.entries()) {
@@ -152,6 +170,8 @@ const uxLookupPromise = settingsPromise.then(async ({ getSetting }) => {
152170
}
153171
return createIssueUXLookup(settings)
154172
})
173+
// mark uxLookupInit as handled
174+
uxLookupInit.catch(() => {})
155175

156176
// shadow `npm` and `npx` to mitigate subshells
157177
require('./link.cjs')(fs.realpathSync(path.join(__dirname, 'bin')), 'npm')
@@ -506,7 +526,7 @@ async function packagesHaveRiskyIssues (safeArb, _registry, pkgs, ora = null, _i
506526
const pkgDatas = []
507527
try {
508528
// TODO: determine org based on cwd, pass in
509-
const uxLookup = await uxLookupPromise
529+
const uxLookup = await uxLookupInit
510530

511531
for await (const pkgData of batchScan(pkgs.map(pkg => pkg.pkgid))) {
512532
/**

lib/shadow/tty-server.cjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
const path = require('path')
22
const { PassThrough } = require('stream')
3-
const { isErrnoException } = require('../utils/type-helpers.cjs')
3+
44
const ipc_version = require('../../package.json').version
5+
const { isErrnoException } = require('../utils/type-helpers.cjs')
56

67
/**
78
* @typedef {import('stream').Readable} Readable
@@ -22,7 +23,7 @@ module.exports = async function createTTYServer (colorLevel, isInteractive, npml
2223
* @type {import('readline')}
2324
*/
2425
let readline
25-
const isSTDINInteractive = true || isInteractive
26+
const isSTDINInteractive = isInteractive
2627
if (!isSTDINInteractive && TTY_IPC) {
2728
return {
2829
async captureTTY (mutexFn) {

lib/utils/sdk.js

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,47 @@ let defaultKey
2020

2121
/** @returns {string | undefined} */
2222
export function getDefaultKey () {
23-
defaultKey = getSetting('apiKey') || process.env['SOCKET_SECURITY_API_KEY'] || defaultKey
23+
defaultKey = process.env['SOCKET_SECURITY_API_KEY'] || getSetting('apiKey') || defaultKey
2424
return defaultKey
2525
}
2626

27+
/**
28+
* The API server that should be used for operations
29+
*
30+
* @type {string | undefined}
31+
*/
32+
let defaultAPIBaseUrl
33+
34+
/**
35+
* @returns {string | undefined}
36+
*/
37+
export function getDefaultAPIBaseUrl () {
38+
defaultAPIBaseUrl = process.env['SOCKET_SECURITY_API_BASE_URL'] || getSetting('apiBaseUrl') || undefined
39+
return defaultAPIBaseUrl
40+
}
41+
42+
/**
43+
* The API server that should be used for operations
44+
*
45+
* @type {string | undefined}
46+
*/
47+
let defaultApiProxy
48+
49+
/**
50+
* @returns {string | undefined}
51+
*/
52+
export function getDefaultHTTPProxy () {
53+
defaultApiProxy = process.env['SOCKET_SECURITY_API_PROXY'] || getSetting('apiProxy') || undefined
54+
return defaultApiProxy
55+
}
56+
2757
/**
2858
* @param {string} [apiKey]
59+
* @param {string} [apiBaseUrl]
60+
* @param {string} [proxy]
2961
* @returns {Promise<import('@socketsecurity/sdk').SocketSdk>}
3062
*/
31-
export async function setupSdk (apiKey = getDefaultKey()) {
63+
export async function setupSdk (apiKey = getDefaultKey(), apiBaseUrl = getDefaultAPIBaseUrl(), proxy = getDefaultHTTPProxy()) {
3264
if (apiKey == null && isInteractive()) {
3365
/**
3466
* @type {{ apiKey: string }}
@@ -49,11 +81,11 @@ export async function setupSdk (apiKey = getDefaultKey()) {
4981
/** @type {import('@socketsecurity/sdk').SocketSdkOptions["agent"]} */
5082
let agent
5183

52-
if (process.env['SOCKET_SECURITY_API_PROXY']) {
84+
if (proxy) {
5385
const { HttpProxyAgent, HttpsProxyAgent } = await import('hpagent')
5486
agent = {
55-
http: new HttpProxyAgent({ proxy: process.env['SOCKET_SECURITY_API_PROXY'] }),
56-
https: new HttpsProxyAgent({ proxy: process.env['SOCKET_SECURITY_API_PROXY'] }),
87+
http: new HttpProxyAgent({ proxy }),
88+
https: new HttpsProxyAgent({ proxy }),
5789
}
5890
}
5991
const packageJsonPath = join(dirname(fileURLToPath(import.meta.url)), '../../package.json')
@@ -62,7 +94,7 @@ export async function setupSdk (apiKey = getDefaultKey()) {
6294
/** @type {import('@socketsecurity/sdk').SocketSdkOptions} */
6395
const sdkOptions = {
6496
agent,
65-
baseUrl: process.env['SOCKET_SECURITY_API_BASE_URL'],
97+
baseUrl: apiBaseUrl,
6698
userAgent: createUserAgentFromPkgJson(JSON.parse(packageJson))
6799
}
68100

lib/utils/settings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const settingsPath = path.join(dataHome, 'socket', 'settings')
2323
* @typedef {Record<string, boolean | {action: 'error' | 'warn' | 'ignore' | 'defer'}>} IssueRules
2424
*/
2525

26-
/** @type {{apiKey?: string | null, enforcedOrgs?: string[] | null}} */
26+
/** @type {{apiKey?: string | null, enforcedOrgs?: string[] | null, apiBaseUrl?: string | null, apiProxy?: string | null}} */
2727
let settings = {}
2828

2929
if (fs.existsSync(settingsPath)) {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"dependencies": {
3+
}
4+
}

0 commit comments

Comments
 (0)