-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathcli-auth.ts
More file actions
288 lines (263 loc) · 10 KB
/
Copy pathcli-auth.ts
File metadata and controls
288 lines (263 loc) · 10 KB
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/**
* CLI Authentication Helpers
*
* Shared utilities for parsing authentication options from CLI commands
* and preparing them for use with the Synapse SDK.
*/
import type { Chain, Synapse } from '@filoz/synapse-sdk'
import { getRpcUrl, NETWORK_CHAINS, resolveDevnetConfig } from '../common/get-rpc-url.js'
import { getOwsAccount } from '../core/ows/index.js'
import type { SynapseSetupConfig } from '../core/synapse/index.js'
import { calibration, initializeSynapse } from '../core/synapse/index.js'
import { createLogger } from '../logger.js'
/**
* Common CLI authentication options interface
* Used across all commands that require authentication
*/
export interface CLIAuthOptions {
/** Private key for standard authentication */
privateKey?: string | undefined
/** OpenWallet Standard wallet name or ID (signs in-process, key stays in vault) */
wallet?: string | undefined
/** Optional passphrase for an OWS-managed wallet */
walletPassphrase?: string | undefined
/** Wallet address for session key mode */
walletAddress?: string | undefined
/** Session key private key */
sessionKey?: string | undefined
/** View-only wallet address (no signing) */
viewAddress?: string | undefined
/** Filecoin network: mainnet or calibration */
network?: string | undefined
/** RPC endpoint URL (overrides network if specified) */
rpcUrl?: string | undefined
/**
* Provider ID overrides. Holds values from the canonical repeatable
* `--provider-id` flag and the deprecated comma-separated `--provider-ids`
* alias, which the CLI layer merges into this array at parse time.
*/
providerIds?: string[] | undefined
/**
* Data set ID overrides. Holds values from the canonical repeatable
* `--data-set-id` flag and the deprecated `--data-set-ids` (comma-separated)
* and `--data-set` (single-value) aliases, which the CLI layer merges into
* this array at parse time.
*/
dataSetIds?: string[] | undefined
}
/**
* Parse CLI authentication options into SynapseSetupConfig
*
* This function handles reading from CLI options and environment variables,
* and returns a config ready for initializeSynapse().
*
* Note: Validation is performed by initializeSynapse() via validateAuthConfig()
*
* @param options - CLI authentication options
* @returns Synapse setup config (validation happens in initializeSynapse)
*/
export async function parseCLIAuth(options: CLIAuthOptions): Promise<SynapseSetupConfig> {
const network = options.network?.toLowerCase().trim()
const isDevnet = network === 'devnet'
const hasRpcUrl = options.rpcUrl != null && options.rpcUrl !== ''
// Env vars are bound to the Commander options via .env() (see cli-options.ts),
// so read everything from `options` rather than process.env here.
const owsWalletId = options.wallet
const owsPassphrase = options.walletPassphrase
// For devnet, fall back to the devnet user's private key if none provided.
// OWS wallets take precedence over PRIVATE_KEY when explicitly supplied.
const privateKey = owsWalletId
? undefined
: options.privateKey || (isDevnet ? resolveDevnetConfig().privateKey : undefined)
const walletAddress = options.walletAddress
const sessionKey = options.sessionKey
const viewAddress = options.viewAddress
const rpcUrl = getRpcUrl(options)
// --network and --rpc-url are mutually exclusive at the Commander level. Set the chain hint
// only when --network was chosen; otherwise leave it undefined and let initializeSynapse probe
// the RPC endpoint. When neither is supplied, default to mainnet.
let chain: Chain | undefined
if (isDevnet) {
chain = resolveDevnetConfig().chain
} else if (network) {
chain = NETWORK_CHAINS[network as keyof typeof NETWORK_CHAINS]
} else if (!hasRpcUrl) {
chain = NETWORK_CHAINS.mainnet
}
// Resolve a single auth mode; initializeSynapse() validates the final shape.
// Precedence mirrors initializeSynapse: read-only, then session key, then an
// owner signer (OWS, then private key). View-only and session-key modes never
// use the owner account, so the OWS account (which lazily loads the native
// adapter and can fail on platforms without a prebuilt) is resolved only when
// an owner signer is actually needed.
const config: {
privateKey?: string
walletAddress?: string
sessionKey?: string
readOnly?: boolean
rpcUrl?: string
chain?: Chain
account?: Awaited<ReturnType<typeof getOwsAccount>>
} = {}
if (viewAddress) {
config.walletAddress = viewAddress
config.readOnly = true
} else if (walletAddress && sessionKey) {
config.walletAddress = walletAddress
config.sessionKey = sessionKey
} else if (owsWalletId) {
const owsOptions: Parameters<typeof getOwsAccount>[0] = {
walletId: owsWalletId,
chain: chain ?? calibration,
}
if (owsPassphrase != null) owsOptions.passphrase = owsPassphrase
config.account = await getOwsAccount(owsOptions)
} else if (privateKey) {
config.privateKey = privateKey
} else if (walletAddress) {
// Only one half of session-key auth supplied; pass it through so
// initializeSynapse can emit its targeted "requires both" error.
config.walletAddress = walletAddress
} else if (sessionKey) {
config.sessionKey = sessionKey
}
if (rpcUrl) config.rpcUrl = rpcUrl
if (chain) config.chain = chain
return config as SynapseSetupConfig
}
/**
* Context selection options for upload (provider IDs and/or data set IDs)
*/
export interface ContextSelectionOptions {
/** Provider ID overrides for targeting specific providers */
providerIds?: bigint[]
/** Data set ID overrides for targeting specific data sets */
dataSetIds?: bigint[]
}
/**
* Validate and deduplicate raw ID strings into a bigint[].
* Each raw value may itself be comma-separated (aliases/env supply lists).
* Returns bigint[] since all downstream consumers (SDK, contracts) use bigint.
* Throws on empty input, non-numeric values, or duplicate IDs.
*/
function toIdList(rawValues: string[], label: string): bigint[] {
const parts = rawValues.flatMap((value) =>
value
.split(',')
.map((s) => s.trim())
.filter((s) => s !== '')
)
if (parts.length === 0) {
throw new Error(`Invalid ${label}: no IDs provided. Provide one or more numeric IDs.`)
}
const ids: bigint[] = []
for (const part of parts) {
if (!/^\d+$/.test(part)) {
throw new Error(`Invalid ${label}: "${part}". Provide positive numeric IDs.`)
}
const id = BigInt(part)
if (id <= 0n) {
throw new Error(`Invalid ${label}: "${part}". Provide positive numeric IDs.`)
}
ids.push(id)
}
const unique = [...new Set(ids)]
if (unique.length !== ids.length) {
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i)
throw new Error(`Duplicate ${label}: ${[...new Set(dupes)].join(', ')}`)
}
return ids
}
interface IdSelectionSource {
/**
* Values from the canonical flag. The CLI layer already merges the deprecated
* aliases into this array (see `collectDeprecatedAliasId` in cli-options.ts),
* so it covers both the canonical flag and every deprecated alias.
*/
canonical?: string[] | undefined
/** Value from the environment variable */
env?: string | undefined
label: string
}
/**
* Gather IDs from the canonical flag (which already includes any deprecated
* alias values) and env, in that precedence: the flag fully replaces env rather
* than merging. Returns `provided: false` when no source supplied any value.
*/
function gatherIdSelection(source: IdSelectionSource): { provided: boolean; ids: bigint[] } {
const raw: string[] = []
if (source.canonical != null && source.canonical.length > 0) {
raw.push(...source.canonical)
} else {
const env = source.env?.trim()
if (env != null && env !== '') {
raw.push(env)
}
}
if (raw.length === 0) {
return { provided: false, ids: [] }
}
return { provided: true, ids: toIdList(raw, source.label) }
}
/**
* Parse provider IDs from `--provider-id` (repeatable), the deprecated
* `--provider-ids` alias, and the `PROVIDER_IDS` env var.
*/
export function parseProviderIdSelection(options?: CLIAuthOptions): bigint[] {
return gatherIdSelection({
canonical: options?.providerIds,
env: process.env.PROVIDER_IDS,
label: 'provider ID(s)',
}).ids
}
/**
* Parse data set IDs from `--data-set-id` (repeatable), the deprecated
* `--data-set-ids` / `--data-set` aliases, and the `DATA_SET_IDS` env var.
*/
export function parseDataSetIdSelection(options?: CLIAuthOptions): bigint[] {
return gatherIdSelection({
canonical: options?.dataSetIds,
env: process.env.DATA_SET_IDS,
label: 'data set ID(s)',
}).ids
}
/**
* Parse context selection from CLI options and environment variables.
*
* Reads provider IDs from `--provider-id` / `PROVIDER_IDS` and data set IDs
* from `--data-set-id` / `DATA_SET_IDS`. The deprecated `--provider-ids`,
* `--data-set-ids`, and `--data-set` aliases are still accepted (with a
* warning). Provider and data set selection are mutually exclusive.
*
* @param options - CLI authentication options (may contain provider/data-set fields)
* @returns Context selection options
*/
export function parseContextSelectionOptions(options?: CLIAuthOptions): ContextSelectionOptions {
const providerIds = parseProviderIdSelection(options)
const dataSetIds = parseDataSetIdSelection(options)
if (providerIds.length > 0 && dataSetIds.length > 0) {
throw new Error(
'Cannot specify both provider IDs (--provider-id/PROVIDER_IDS) and data set IDs (--data-set-id/DATA_SET_IDS). Use one or the other.'
)
}
if (providerIds.length > 0) {
return { providerIds }
}
if (dataSetIds.length > 0) {
return { dataSetIds }
}
return {}
}
/**
* Get a logger instance for use in CLI commands
*
* @returns Logger configured for CLI use
*/
export function getCLILogger() {
return createLogger({ logLevel: process.env.LOG_LEVEL })
}
export async function getCliSynapse(options: CLIAuthOptions): Promise<Synapse> {
const authConfig = await parseCLIAuth(options)
const logger = getCLILogger()
return initializeSynapse(authConfig, logger)
}