Fix --account N credential priority across all SDKs
--account N was silently ignored when UNSANDBOX_PUBLIC_KEY/SECRET_KEY env vars were set. The credential resolution checked env vars at tier 2 before ever reaching the CSV lookup, so the explicit flag had no effect. Correct priority order (all 8 SDKs): 1. CLI -p/-k flags (explicit key args) 2. --account N → direct CSV row lookup (bypasses env vars) 3. UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY env vars 4. ~/.unsandbox/accounts.csv default (row 0 or UNSANDBOX_ACCOUNT) SDKs updated: C, Python, Go, JavaScript, Ruby, PHP, Java, Rust Also adds: - --account N flag to CLI parsers in all 8 SDKs (Go, PHP, Java, Rust previously had no flag at all; Python/JS/Ruby had the parameter but never wired it to the CLI) - test_account_flag.sh integration test for each SDK verifying the priority behavior with real and garbage credentials - test-integration Makefile target for the C SDK
This commit is contained in:
parent
8093386568
commit
ed6c52b001
20 changed files with 1243 additions and 174 deletions
|
|
@ -331,10 +331,11 @@ function loadCredentialsFromStorage() {
|
|||
*
|
||||
* Priority:
|
||||
* 1. Function arguments
|
||||
* 2. Environment variables (Node.js)
|
||||
* 3. localStorage (Browser)
|
||||
* 4. ~/.unsandbox/accounts.csv (Node.js)
|
||||
* 5. ./accounts.csv (Node.js)
|
||||
* 2. accountIndex >= 0 → load from accounts.csv row N
|
||||
* 3. Environment variables (Node.js)
|
||||
* 4. localStorage (Browser)
|
||||
* 5. ~/.unsandbox/accounts.csv (default row, Node.js)
|
||||
* 6. ./accounts.csv (default row, Node.js)
|
||||
*/
|
||||
function resolveCredentials(publicKey, secretKey, accountIndex) {
|
||||
// Tier 1: Function arguments
|
||||
|
|
@ -342,7 +343,26 @@ function resolveCredentials(publicKey, secretKey, accountIndex) {
|
|||
return [publicKey, secretKey];
|
||||
}
|
||||
|
||||
// Tier 2: Environment variables (Node.js only)
|
||||
// Tier 2: Explicit accountIndex → load from accounts.csv row N (Node.js only)
|
||||
if (IS_NODE && fs && path && accountIndex !== undefined && accountIndex >= 0) {
|
||||
// ~/.unsandbox/accounts.csv first
|
||||
try {
|
||||
const unsandboxDir = getUnsandboxDir();
|
||||
const creds = loadCredentialsFromCsv(path.join(unsandboxDir, 'accounts.csv'), accountIndex);
|
||||
if (creds) {
|
||||
return creds;
|
||||
}
|
||||
} catch (e) {
|
||||
// Continue to next location
|
||||
}
|
||||
// ./accounts.csv fallback
|
||||
const creds = loadCredentialsFromCsv('accounts.csv', accountIndex);
|
||||
if (creds) {
|
||||
return creds;
|
||||
}
|
||||
}
|
||||
|
||||
// Tier 3: Environment variables (Node.js only)
|
||||
if (IS_NODE) {
|
||||
const envPk = process.env.UNSANDBOX_PUBLIC_KEY;
|
||||
const envSk = process.env.UNSANDBOX_SECRET_KEY;
|
||||
|
|
@ -351,7 +371,7 @@ function resolveCredentials(publicKey, secretKey, accountIndex) {
|
|||
}
|
||||
}
|
||||
|
||||
// Tier 3: localStorage (Browser only)
|
||||
// Tier 4: localStorage (Browser only)
|
||||
if (IS_BROWSER) {
|
||||
const storageCreds = loadCredentialsFromStorage();
|
||||
if (storageCreds) {
|
||||
|
|
@ -359,17 +379,14 @@ function resolveCredentials(publicKey, secretKey, accountIndex) {
|
|||
}
|
||||
}
|
||||
|
||||
// Tier 4 & 5: File-based credentials (Node.js only)
|
||||
// Tier 5 & 6: File-based credentials with default index (Node.js only)
|
||||
if (IS_NODE && fs && path) {
|
||||
// Determine account index
|
||||
if (accountIndex === undefined) {
|
||||
accountIndex = parseInt(process.env.UNSANDBOX_ACCOUNT || '0', 10);
|
||||
}
|
||||
const defaultIndex = parseInt(process.env.UNSANDBOX_ACCOUNT || '0', 10);
|
||||
|
||||
// Tier 4: ~/.unsandbox/accounts.csv
|
||||
// Tier 5: ~/.unsandbox/accounts.csv
|
||||
try {
|
||||
const unsandboxDir = getUnsandboxDir();
|
||||
const creds = loadCredentialsFromCsv(path.join(unsandboxDir, 'accounts.csv'), accountIndex);
|
||||
const creds = loadCredentialsFromCsv(path.join(unsandboxDir, 'accounts.csv'), defaultIndex);
|
||||
if (creds) {
|
||||
return creds;
|
||||
}
|
||||
|
|
@ -377,8 +394,8 @@ function resolveCredentials(publicKey, secretKey, accountIndex) {
|
|||
// Continue to next tier
|
||||
}
|
||||
|
||||
// Tier 5: ./accounts.csv
|
||||
const creds = loadCredentialsFromCsv('accounts.csv', accountIndex);
|
||||
// Tier 6: ./accounts.csv
|
||||
const creds = loadCredentialsFromCsv('accounts.csv', defaultIndex);
|
||||
if (creds) {
|
||||
return creds;
|
||||
}
|
||||
|
|
@ -2193,6 +2210,7 @@ function parseArgs(args) {
|
|||
output: null,
|
||||
publicKey: null,
|
||||
secretKey: null,
|
||||
accountIndex: undefined,
|
||||
network: 'zerotrust',
|
||||
vcpu: 1,
|
||||
yes: false,
|
||||
|
|
@ -2322,6 +2340,9 @@ function parseArgs(args) {
|
|||
} else if (arg === '-k' || arg === '--secret-key') {
|
||||
result.secretKey = args[++i];
|
||||
i++;
|
||||
} else if (arg === '--account') {
|
||||
result.accountIndex = parseInt(args[++i], 10);
|
||||
i++;
|
||||
} else if (arg === '-n' || arg === '--network') {
|
||||
result.network = args[++i];
|
||||
i++;
|
||||
|
|
@ -2509,8 +2530,7 @@ function formatTable(items, columns) {
|
|||
* Handle session commands.
|
||||
*/
|
||||
async function handleSession(opts) {
|
||||
const pk = opts.publicKey;
|
||||
const sk = opts.secretKey;
|
||||
let [pk, sk] = resolveCredentials(opts.publicKey, opts.secretKey, opts.accountIndex);
|
||||
|
||||
// List sessions
|
||||
if (opts.list) {
|
||||
|
|
@ -2593,8 +2613,7 @@ async function handleSession(opts) {
|
|||
* Handle service commands.
|
||||
*/
|
||||
async function handleService(opts) {
|
||||
const pk = opts.publicKey;
|
||||
const sk = opts.secretKey;
|
||||
let [pk, sk] = resolveCredentials(opts.publicKey, opts.secretKey, opts.accountIndex);
|
||||
|
||||
// Handle env subcommand
|
||||
if (opts.subcommand === 'env') {
|
||||
|
|
@ -2829,8 +2848,7 @@ async function handleService(opts) {
|
|||
* Handle snapshot commands.
|
||||
*/
|
||||
async function handleSnapshot(opts) {
|
||||
const pk = opts.publicKey;
|
||||
const sk = opts.secretKey;
|
||||
let [pk, sk] = resolveCredentials(opts.publicKey, opts.secretKey, opts.accountIndex);
|
||||
|
||||
// List snapshots
|
||||
if (opts.list) {
|
||||
|
|
@ -2898,8 +2916,7 @@ async function handleSnapshot(opts) {
|
|||
* Handle image command.
|
||||
*/
|
||||
async function handleImage(opts) {
|
||||
const pk = opts.publicKey;
|
||||
const sk = opts.secretKey;
|
||||
let [pk, sk] = resolveCredentials(opts.publicKey, opts.secretKey, opts.accountIndex);
|
||||
|
||||
// List images
|
||||
if (opts.list) {
|
||||
|
|
@ -3002,13 +3019,13 @@ async function handleImage(opts) {
|
|||
* Handle key command.
|
||||
*/
|
||||
async function handleKey(opts) {
|
||||
const [pk, sk] = resolveCredentials(opts.publicKey, opts.secretKey, opts.accountIndex);
|
||||
try {
|
||||
const result = await validateKeys(opts.publicKey, opts.secretKey);
|
||||
const result = await validateKeys(pk, sk);
|
||||
console.log('API Key Status:');
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} catch (err) {
|
||||
// If validate endpoint doesn't exist, just show that credentials were resolved
|
||||
const [pk] = resolveCredentials(opts.publicKey, opts.secretKey);
|
||||
console.log(`Public Key: ${pk}`);
|
||||
console.log('Key validation endpoint returned error - key may still be valid.');
|
||||
}
|
||||
|
|
@ -3018,7 +3035,8 @@ async function handleKey(opts) {
|
|||
* Handle languages command.
|
||||
*/
|
||||
async function handleLanguages(opts) {
|
||||
const languages = await getLanguages(opts.publicKey, opts.secretKey);
|
||||
const [pk, sk] = resolveCredentials(opts.publicKey, opts.secretKey, opts.accountIndex);
|
||||
const languages = await getLanguages(pk, sk);
|
||||
|
||||
if (opts.json) {
|
||||
// Output as JSON array
|
||||
|
|
@ -3035,8 +3053,7 @@ async function handleLanguages(opts) {
|
|||
* Handle execute command (default).
|
||||
*/
|
||||
async function handleExecute(opts) {
|
||||
const pk = opts.publicKey;
|
||||
const sk = opts.secretKey;
|
||||
let [pk, sk] = resolveCredentials(opts.publicKey, opts.secretKey, opts.accountIndex);
|
||||
|
||||
let code;
|
||||
let language;
|
||||
|
|
|
|||
108
clients/javascript/sync/tests/test_account_flag.sh
Executable file
108
clients/javascript/sync/tests/test_account_flag.sh
Executable file
|
|
@ -0,0 +1,108 @@
|
|||
#!/usr/bin/env bash
|
||||
# Integration test for --account N credential selection in un.js.
|
||||
#
|
||||
# Tests that --account N selects the correct row from accounts.csv,
|
||||
# taking priority over environment variables.
|
||||
#
|
||||
# Requires UNSANDBOX_PUBLIC_KEY and UNSANDBOX_SECRET_KEY to be set.
|
||||
# Skips if credentials are not available.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
UN_JS="$SCRIPT_DIR/../src/un.js"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
SKIP=0
|
||||
|
||||
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
|
||||
fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); }
|
||||
skip() { echo "SKIP: $1"; SKIP=$((SKIP + 1)); }
|
||||
|
||||
# Require real credentials from environment
|
||||
if [ -z "${UNSANDBOX_PUBLIC_KEY:-}" ] || [ -z "${UNSANDBOX_SECRET_KEY:-}" ]; then
|
||||
skip "UNSANDBOX_PUBLIC_KEY / UNSANDBOX_SECRET_KEY not set"
|
||||
echo "Results: PASS=$PASS FAIL=$FAIL SKIP=$SKIP"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
REAL_PK="$UNSANDBOX_PUBLIC_KEY"
|
||||
REAL_SK="$UNSANDBOX_SECRET_KEY"
|
||||
GARBAGE_PK="unsb-pk-0000-0000-0000-garbage"
|
||||
GARBAGE_SK="unsb-sk-00000-00000-00000-garbage"
|
||||
|
||||
# Build a temporary HOME with accounts.csv: row 0 = garbage, row 1 = real creds
|
||||
TMPHOME="$(mktemp -d)"
|
||||
mkdir -p "$TMPHOME/.unsandbox"
|
||||
# Header + row 0 (garbage) + row 1 (real)
|
||||
printf 'public_key,secret_key\n%s,%s\n%s,%s\n' \
|
||||
"$GARBAGE_PK" "$GARBAGE_SK" \
|
||||
"$REAL_PK" "$REAL_SK" \
|
||||
> "$TMPHOME/.unsandbox/accounts.csv"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMPHOME"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 1: HOME=TMPHOME, env vars = garbage, --account 1 => row 1 = real creds
|
||||
# Expect: key command succeeds (no auth error)
|
||||
# ---------------------------------------------------------------------------
|
||||
if HOME="$TMPHOME" \
|
||||
UNSANDBOX_PUBLIC_KEY="$GARBAGE_PK" \
|
||||
UNSANDBOX_SECRET_KEY="$GARBAGE_SK" \
|
||||
node "$UN_JS" --account 1 key 2>&1 | grep -qiE 'Public Key|key_id|account|status'; then
|
||||
pass "Test 1: --account 1 selects row 1 (real creds) over env garbage"
|
||||
else
|
||||
# Also accept a successful JSON response (key validates)
|
||||
output="$(HOME="$TMPHOME" \
|
||||
UNSANDBOX_PUBLIC_KEY="$GARBAGE_PK" \
|
||||
UNSANDBOX_SECRET_KEY="$GARBAGE_SK" \
|
||||
node "$UN_JS" --account 1 key 2>&1 || true)"
|
||||
if echo "$output" | grep -qiE '401|invalid|unauthorized|authentication'; then
|
||||
fail "Test 1: --account 1 selected row 1 but auth failed (real creds may be invalid)"
|
||||
else
|
||||
pass "Test 1: --account 1 selects row 1 (real creds) over env garbage"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 2: HOME=TMPHOME, env vars = real, --account 0 => row 0 = garbage creds
|
||||
# Expect: 401 / auth error (garbage creds used despite real env vars)
|
||||
# ---------------------------------------------------------------------------
|
||||
output2="$(HOME="$TMPHOME" \
|
||||
UNSANDBOX_PUBLIC_KEY="$REAL_PK" \
|
||||
UNSANDBOX_SECRET_KEY="$REAL_SK" \
|
||||
node "$UN_JS" --account 0 key 2>&1 || true)"
|
||||
if echo "$output2" | grep -qiE '401|invalid|unauthorized|authentication|error'; then
|
||||
pass "Test 2: --account 0 selects row 0 (garbage) over env real creds (expected auth failure)"
|
||||
else
|
||||
fail "Test 2: --account 0 should use garbage creds and fail auth, but got: $output2"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test 3: HOME=TMPHOME, env vars = real, no --account => env vars win (real creds)
|
||||
# Expect: key command succeeds
|
||||
# ---------------------------------------------------------------------------
|
||||
output3="$(HOME="$TMPHOME" \
|
||||
UNSANDBOX_PUBLIC_KEY="$REAL_PK" \
|
||||
UNSANDBOX_SECRET_KEY="$REAL_SK" \
|
||||
node "$UN_JS" key 2>&1 || true)"
|
||||
if echo "$output3" | grep -qiE '401|invalid|unauthorized|authentication failed'; then
|
||||
fail "Test 3: without --account, env real creds should succeed but got auth error: $output3"
|
||||
else
|
||||
pass "Test 3: without --account, env vars (real creds) are used"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Summary
|
||||
# ---------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "Results: PASS=$PASS FAIL=$FAIL SKIP=$SKIP"
|
||||
|
||||
if [ "$FAIL" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue