-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathconfig.ts
204 lines (188 loc) · 6.45 KB
/
config.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import process from 'node:process'
import config from '@socketsecurity/config'
import { logger } from '@socketsecurity/registry/lib/logger'
import { safeReadFileSync } from './fs'
import constants from '../constants'
export interface LocalConfig {
apiBaseUrl?: string | null | undefined
// @deprecated ; use apiToken. when loading a config, if this prop exists it
// is deleted and set to apiToken instead, and then persisted.
// should only happen once for legacy users.
apiKey?: string | null | undefined
apiProxy?: string | null | undefined
apiToken?: string | null | undefined
defaultOrg?: string
enforcedOrgs?: string[] | readonly string[] | null | undefined
// Ignore.
test?: unknown
}
// Default app data folder env var on Win
const LOCALAPPDATA = 'LOCALAPPDATA'
// Default app data folder env var on Mac/Linux
const XDG_DATA_HOME = 'XDG_DATA_HOME'
const SOCKET_APP_DIR = 'socket/settings' // It used to be settings...
export const supportedConfigKeys: Map<keyof LocalConfig, string> = new Map([
['apiBaseUrl', 'Base URL of the API endpoint'],
['apiProxy', 'A proxy through which to access the API'],
['apiToken', 'The API token required to access most API endpoints'],
[
'defaultOrg',
'The default org slug to use when appropriate; usually the org your API token has access to. When set, all orgSlug arguments are implied to be this value.'
],
[
'enforcedOrgs',
'Orgs in this list have their security policies enforced on this machine'
]
])
export const sensitiveConfigKeys: Set<keyof LocalConfig> = new Set(['apiToken'])
let cachedConfig: LocalConfig | undefined
// When using --config or SOCKET_CLI_CONFIG_OVERRIDE, do not persist the config
let readOnlyConfig = false
let configPath: string | undefined
let warnedConfigPathWin32Missing = false
let pendingSave = false
export function overrideCachedConfig(config: unknown) {
cachedConfig = config as LocalConfig
readOnlyConfig = true
// Normalize apiKey to apiToken
if (cachedConfig['apiKey']) {
cachedConfig['apiToken'] = cachedConfig['apiKey']
delete cachedConfig['apiKey']
}
}
function getConfigValues(): LocalConfig {
if (cachedConfig === undefined) {
cachedConfig = {} as LocalConfig
// Order: env var > --config flag > file
const configPath = getConfigPath()
if (configPath) {
const raw = safeReadFileSync(configPath)
if (raw) {
try {
Object.assign(
cachedConfig,
JSON.parse(Buffer.from(raw, 'base64').toString())
)
} catch {
logger.warn(`Failed to parse config at ${configPath}`)
}
// Normalize apiKey to apiToken and persist; one time migration per user
if (cachedConfig['apiKey']) {
const token = cachedConfig['apiKey']
delete cachedConfig['apiKey']
// Persist it
updateConfigValue('apiToken', token)
}
} else {
fs.mkdirSync(path.dirname(configPath), { recursive: true })
}
}
}
return cachedConfig
}
function getConfigPath(): string | undefined {
// Get the OS app data folder:
// - Win: %LOCALAPPDATA% or fail?
// - Mac: %XDG_DATA_HOME% or fallback to "~/Library/Application Support/"
// - Linux: %XDG_DATA_HOME% or fallback to "~/.local/share/"
// Note: LOCALAPPDATA is typically: C:\Users\USERNAME\AppData
// Note: XDG stands for "X Desktop Group", nowadays "freedesktop.org"
// On most systems that path is: $HOME/.local/share
// Then append `socket/settings`, so:
// - Win: %LOCALAPPDATA%\socket\settings or return undefined
// - Mac: %XDG_DATA_HOME%/socket/settings or "~/Library/Application Support/socket/settings"
// - Linux: %XDG_DATA_HOME%/socket/settings or "~/.local/share/socket/settings"
if (configPath === undefined) {
// Lazily access constants.WIN32.
const { WIN32 } = constants
let dataHome: string | undefined = WIN32
? process.env[LOCALAPPDATA]
: process.env[XDG_DATA_HOME]
if (!dataHome) {
if (WIN32) {
if (!warnedConfigPathWin32Missing) {
warnedConfigPathWin32Missing = true
logger.warn(`Missing %${LOCALAPPDATA}%`)
}
} else {
dataHome = path.join(
os.homedir(),
...(process.platform === 'darwin'
? ['Library', 'Application Support']
: ['.local', 'share'])
)
}
}
configPath = dataHome ? path.join(dataHome, SOCKET_APP_DIR) : undefined
}
return configPath
}
function normalizeConfigKey(key: keyof LocalConfig): keyof LocalConfig {
// Note: apiKey was the old name of the token. When we load a config with
// property apiKey, we'll copy that to apiToken and delete the old prop
const normalizedKey = key === 'apiKey' ? 'apiToken' : key
if (
normalizedKey !== 'test' &&
!supportedConfigKeys.has(normalizedKey as keyof LocalConfig)
) {
throw new Error(`Invalid config key: ${normalizedKey}`)
}
return normalizedKey as keyof LocalConfig
}
export function findSocketYmlSync() {
let prevDir = null
let dir = process.cwd()
while (dir !== prevDir) {
let ymlPath = path.join(dir, 'socket.yml')
let yml = safeReadFileSync(ymlPath)
if (yml === undefined) {
ymlPath = path.join(dir, 'socket.yaml')
yml = safeReadFileSync(ymlPath)
}
if (typeof yml === 'string') {
try {
return {
path: ymlPath,
parsed: config.parseSocketConfig(yml)
}
} catch {
throw new Error(`Found file but was unable to parse ${ymlPath}`)
}
}
prevDir = dir
dir = path.join(dir, '..')
}
return null
}
export function getConfigValue<Key extends keyof LocalConfig>(
key: Key
): LocalConfig[Key] {
return getConfigValues()[normalizeConfigKey(key) as Key]
}
export function updateConfigValue<Key extends keyof LocalConfig>(
key: keyof LocalConfig,
value: LocalConfig[Key]
): void {
const localConfig = getConfigValues()
localConfig[normalizeConfigKey(key) as Key] = value
if (readOnlyConfig) {
logger.error(
'Not persisting config change; current config overridden through env var or flag'
)
} else if (!pendingSave) {
pendingSave = true
process.nextTick(() => {
pendingSave = false
const configPath = getConfigPath()
if (configPath) {
fs.writeFileSync(
configPath,
Buffer.from(JSON.stringify(localConfig)).toString('base64')
)
}
})
}
}