@symbo.ls/cli 2.33.37 → 2.33.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/login.js +38 -7
- package/helpers/credentialManager.js +81 -0
- package/package.json +6 -6
package/bin/login.js
CHANGED
|
@@ -101,6 +101,11 @@ function extractConfirmToken(data) {
|
|
|
101
101
|
)
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
function withCliSessionKeyHeaders(headers, cliSessionKey) {
|
|
105
|
+
if (!cliSessionKey) return headers
|
|
106
|
+
return { ...headers, 'X-Symbols-Cli-Session-Key': cliSessionKey }
|
|
107
|
+
}
|
|
108
|
+
|
|
104
109
|
program
|
|
105
110
|
.command('login')
|
|
106
111
|
.description('Sign in to Symbols')
|
|
@@ -111,12 +116,18 @@ program
|
|
|
111
116
|
|
|
112
117
|
// Prompt for credentials
|
|
113
118
|
const currentConfig = loadCliConfig()
|
|
119
|
+
const credManager = new CredentialManager()
|
|
120
|
+
// If the user selected an active server via `smbls servers -s`, prefer that as the login default.
|
|
121
|
+
// Env vars should still override everything (useful for CI / debugging).
|
|
122
|
+
const envApiBaseUrl = process.env.SYMBOLS_API_BASE_URL || process.env.SMBLS_API_URL
|
|
123
|
+
const defaultApiBaseUrl =
|
|
124
|
+
envApiBaseUrl || credManager.getCurrentApiBaseUrl() || currentConfig.apiBaseUrl || getApiUrl()
|
|
114
125
|
const first = await inquirer.prompt([
|
|
115
126
|
{
|
|
116
127
|
type: 'input',
|
|
117
128
|
name: 'apiBaseUrl',
|
|
118
129
|
message: 'API Base URL:',
|
|
119
|
-
default:
|
|
130
|
+
default: defaultApiBaseUrl,
|
|
120
131
|
validate: input => /^https?:\/\//.test(input) || '❌ Please enter a valid URL'
|
|
121
132
|
},
|
|
122
133
|
{
|
|
@@ -170,6 +181,21 @@ program
|
|
|
170
181
|
throw err
|
|
171
182
|
}
|
|
172
183
|
} else if (first.method === 'google_browser' || first.method === 'github_browser') {
|
|
184
|
+
const defaultCliSessionKey = credManager.getCliSessionKey(apiBaseUrl) || ''
|
|
185
|
+
const { cliSessionKey } = await inquirer.prompt([
|
|
186
|
+
{
|
|
187
|
+
type: 'input',
|
|
188
|
+
name: 'cliSessionKey',
|
|
189
|
+
message: 'CLI session key (for long-lived tokens, optional):',
|
|
190
|
+
default: defaultCliSessionKey,
|
|
191
|
+
filter: (v) => (v || '').trim()
|
|
192
|
+
}
|
|
193
|
+
])
|
|
194
|
+
|
|
195
|
+
// Persist per-server key so users can change it per environment
|
|
196
|
+
// (empty value clears and disables long-lived token request)
|
|
197
|
+
credManager.setCliSessionKey(apiBaseUrl, cliSessionKey)
|
|
198
|
+
|
|
173
199
|
const sessionId = crypto.randomUUID()
|
|
174
200
|
const codeVerifier = randomVerifier(64)
|
|
175
201
|
const codeChallenge = sha256Base64url(codeVerifier)
|
|
@@ -177,11 +203,12 @@ program
|
|
|
177
203
|
console.log(chalk.dim('\nCreating secure sign-in session...'))
|
|
178
204
|
const sessionResp = await fetch(`${apiBaseUrl}/core/auth/session`, {
|
|
179
205
|
method: 'POST',
|
|
180
|
-
headers: { 'Content-Type': 'application/json' },
|
|
206
|
+
headers: withCliSessionKeyHeaders({ 'Content-Type': 'application/json' }, cliSessionKey),
|
|
181
207
|
body: JSON.stringify({
|
|
182
208
|
session_id: sessionId,
|
|
183
209
|
code_challenge: codeChallenge,
|
|
184
|
-
plugin_info: { version: 'cli', figma_env: 'cli' }
|
|
210
|
+
plugin_info: { version: 'cli', figma_env: 'cli' },
|
|
211
|
+
...(cliSessionKey ? { cli_session_key: cliSessionKey } : {})
|
|
185
212
|
})
|
|
186
213
|
})
|
|
187
214
|
|
|
@@ -214,7 +241,8 @@ program
|
|
|
214
241
|
}
|
|
215
242
|
|
|
216
243
|
const statusResp = await fetch(
|
|
217
|
-
`${apiBaseUrl}/core/auth/session/status?session=${encodeURIComponent(sessionId)}
|
|
244
|
+
`${apiBaseUrl}/core/auth/session/status?session=${encodeURIComponent(sessionId)}`,
|
|
245
|
+
{ headers: withCliSessionKeyHeaders({}, cliSessionKey) }
|
|
218
246
|
)
|
|
219
247
|
const statusData = await statusResp.json().catch(async () => ({ message: await statusResp.text() }))
|
|
220
248
|
|
|
@@ -235,8 +263,12 @@ program
|
|
|
235
263
|
console.log(chalk.dim('Confirming session...'))
|
|
236
264
|
const confirmResp = await fetch(`${apiBaseUrl}/core/auth/session/confirm`, {
|
|
237
265
|
method: 'POST',
|
|
238
|
-
headers: { 'Content-Type': 'application/json' },
|
|
239
|
-
body: JSON.stringify({
|
|
266
|
+
headers: withCliSessionKeyHeaders({ 'Content-Type': 'application/json' }, cliSessionKey),
|
|
267
|
+
body: JSON.stringify({
|
|
268
|
+
session_id: sessionId,
|
|
269
|
+
code_verifier: codeVerifier,
|
|
270
|
+
...(cliSessionKey ? { cli_session_key: cliSessionKey } : {})
|
|
271
|
+
})
|
|
240
272
|
})
|
|
241
273
|
|
|
242
274
|
const confirmData = await confirmResp.json().catch(async () => ({ message: await confirmResp.text() }))
|
|
@@ -262,7 +294,6 @@ program
|
|
|
262
294
|
}
|
|
263
295
|
|
|
264
296
|
// Save credentials
|
|
265
|
-
const credManager = new CredentialManager()
|
|
266
297
|
credManager.saveCredentials({
|
|
267
298
|
apiBaseUrl,
|
|
268
299
|
authToken: accessToken,
|
|
@@ -4,12 +4,33 @@ import os from 'os'
|
|
|
4
4
|
|
|
5
5
|
const RC_FILE = '.smblsrc'
|
|
6
6
|
const DEFAULT_API = 'https://api.symbols.app'
|
|
7
|
+
const DEFAULT_LOCAL_CLI_SESSION_KEY = 'supersecret...change-in-production!!!'
|
|
8
|
+
const DEFAULT_PROD_CLI_SESSION_KEY =
|
|
9
|
+
'r5jMdknqYjuGeWAsApTvPjaYHjWkdyw70veJye11Mb_vInBmsBZ9RM4GKU2_rm17Z2qvNahbyIV5gQVXY0V9DlUYdflLKd1jYCKSuc9r_xreC9hVEovfbZqmfbOl-JFCN1w1MXEsPNWkWj48nfF6IFfoWH-0hsdQlPBFjW1mv10'
|
|
7
10
|
|
|
8
11
|
export class CredentialManager {
|
|
9
12
|
constructor() {
|
|
10
13
|
this.rcPath = path.join(os.homedir(), RC_FILE)
|
|
11
14
|
}
|
|
12
15
|
|
|
16
|
+
static defaultCliSessionKeyForApiBaseUrl(apiBaseUrl) {
|
|
17
|
+
const baseUrl = (apiBaseUrl || '').trim()
|
|
18
|
+
if (!baseUrl) return null
|
|
19
|
+
|
|
20
|
+
// Match the environments used by login.js's websiteFromApi:
|
|
21
|
+
// - production: api.symbols.app -> production session key by default
|
|
22
|
+
// - everything else (dev/staging/test/local/custom): local key by default
|
|
23
|
+
try {
|
|
24
|
+
const u = new URL(baseUrl)
|
|
25
|
+
if (u.host === 'api.symbols.app') return DEFAULT_PROD_CLI_SESSION_KEY
|
|
26
|
+
return DEFAULT_LOCAL_CLI_SESSION_KEY
|
|
27
|
+
} catch (_) {
|
|
28
|
+
// Best-effort fallback if apiBaseUrl is malformed
|
|
29
|
+
if (baseUrl.includes('api.symbols.app')) return DEFAULT_PROD_CLI_SESSION_KEY
|
|
30
|
+
return DEFAULT_LOCAL_CLI_SESSION_KEY
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
13
34
|
// --- Low-level helpers ----------------------------------------------------
|
|
14
35
|
|
|
15
36
|
loadRaw() {
|
|
@@ -89,6 +110,66 @@ export class CredentialManager {
|
|
|
89
110
|
return state.currentApiBaseUrl || DEFAULT_API
|
|
90
111
|
}
|
|
91
112
|
|
|
113
|
+
getCliSessionKey(apiBaseUrl) {
|
|
114
|
+
// Allow overriding via env (useful for CI / debugging)
|
|
115
|
+
const envKey =
|
|
116
|
+
process.env.SYMBOLS_CLI_SESSION_KEY ||
|
|
117
|
+
process.env.SMBLS_CLI_SESSION_KEY ||
|
|
118
|
+
process.env.PLUGIN_CLI_SESSION_KEY
|
|
119
|
+
if (envKey) return envKey
|
|
120
|
+
|
|
121
|
+
const state = this.loadState() || {}
|
|
122
|
+
const baseUrl = apiBaseUrl || state.currentApiBaseUrl || DEFAULT_API
|
|
123
|
+
|
|
124
|
+
// Prefer explicit mapping, then per-profile field
|
|
125
|
+
const mapped = state.cliSessionKeys && typeof state.cliSessionKeys === 'object'
|
|
126
|
+
? state.cliSessionKeys[baseUrl]
|
|
127
|
+
: null
|
|
128
|
+
if (mapped) return mapped
|
|
129
|
+
|
|
130
|
+
const profileKey =
|
|
131
|
+
state.profiles && typeof state.profiles === 'object'
|
|
132
|
+
? state.profiles[baseUrl]?.cliSessionKey
|
|
133
|
+
: null
|
|
134
|
+
if (profileKey) return profileKey
|
|
135
|
+
|
|
136
|
+
// Fall back to environment defaults (e.g. localhost)
|
|
137
|
+
return CredentialManager.defaultCliSessionKeyForApiBaseUrl(baseUrl)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
setCliSessionKey(apiBaseUrl, cliSessionKey) {
|
|
141
|
+
const state = this.loadState() || {}
|
|
142
|
+
const baseUrl = apiBaseUrl || state.currentApiBaseUrl || DEFAULT_API
|
|
143
|
+
const nextKey = (cliSessionKey || '').trim()
|
|
144
|
+
|
|
145
|
+
const next = { ...state }
|
|
146
|
+
|
|
147
|
+
// Store in a dedicated map keyed by API base URL
|
|
148
|
+
const prevMap = (state.cliSessionKeys && typeof state.cliSessionKeys === 'object')
|
|
149
|
+
? state.cliSessionKeys
|
|
150
|
+
: {}
|
|
151
|
+
const nextMap = { ...prevMap }
|
|
152
|
+
|
|
153
|
+
if (!nextKey) {
|
|
154
|
+
delete nextMap[baseUrl]
|
|
155
|
+
} else {
|
|
156
|
+
nextMap[baseUrl] = nextKey
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
next.cliSessionKeys = nextMap
|
|
160
|
+
|
|
161
|
+
// Also mirror into the profile (handy for humans inspecting ~/.smblsrc)
|
|
162
|
+
const profiles = (state.profiles && typeof state.profiles === 'object') ? state.profiles : {}
|
|
163
|
+
const existing = profiles[baseUrl] || {}
|
|
164
|
+
const updatedProfile = { ...existing }
|
|
165
|
+
if (!nextKey) delete updatedProfile.cliSessionKey
|
|
166
|
+
else updatedProfile.cliSessionKey = nextKey
|
|
167
|
+
next.profiles = { ...profiles, [baseUrl]: updatedProfile }
|
|
168
|
+
|
|
169
|
+
this.saveRaw(next)
|
|
170
|
+
return next
|
|
171
|
+
}
|
|
172
|
+
|
|
92
173
|
setCurrentApiBaseUrl(apiBaseUrl) {
|
|
93
174
|
const state = this.loadState()
|
|
94
175
|
const next = { ...state, currentApiBaseUrl: apiBaseUrl || DEFAULT_API }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@symbo.ls/cli",
|
|
3
|
-
"version": "2.33.
|
|
3
|
+
"version": "2.33.38",
|
|
4
4
|
"description": "Fetch your Symbols configuration",
|
|
5
5
|
"main": "bin/fetch.js",
|
|
6
6
|
"author": "Symbols",
|
|
@@ -15,18 +15,18 @@
|
|
|
15
15
|
"vpatch": "npm version patch && npm publish"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@symbo.ls/fetch": "^2.33.
|
|
19
|
-
"@symbo.ls/init": "^2.33.
|
|
20
|
-
"@symbo.ls/socket": "^2.33.
|
|
18
|
+
"@symbo.ls/fetch": "^2.33.38",
|
|
19
|
+
"@symbo.ls/init": "^2.33.38",
|
|
20
|
+
"@symbo.ls/socket": "^2.33.38",
|
|
21
21
|
"chalk": "^5.4.1",
|
|
22
22
|
"chokidar": "^4.0.3",
|
|
23
23
|
"commander": "^13.1.0",
|
|
24
24
|
"diff": "^5.2.0",
|
|
25
|
-
"esbuild": "^0.
|
|
25
|
+
"esbuild": "^0.26.0",
|
|
26
26
|
"inquirer": "^12.9.0",
|
|
27
27
|
"node-fetch": "^3.3.2",
|
|
28
28
|
"socket.io-client": "^4.8.1",
|
|
29
29
|
"v8-compile-cache": "^2.4.0"
|
|
30
30
|
},
|
|
31
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "7048f2fd761c7aa6a4233019c1b91318d88e64ce"
|
|
32
32
|
}
|