Skip to content

Commit a8262f2

Browse files
committed
fix: probe all regions during auth login to prevent false validation failures
Closes #132. A CN key validated against the global quota endpoint returns status_code 2049, and the old catch-all replaced the real error with a generic "API key validation failed" message, hiding the root cause. Now auth login probes every region in parallel (like detectRegion) and picks the first that succeeds; on failure the actual API errors from each region are surfaced. The main.ts region-detection step is skipped for auth login since the command handles it internally.
1 parent 02fbdfb commit a8262f2

2 files changed

Lines changed: 43 additions & 18 deletions

File tree

src/commands/auth/login.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { renderQuotaTable } from '../../output/quota-table';
99

1010
import { getConfigPath } from '../../config/paths';
1111
import { readConfigFile, writeConfigFile } from '../../config/loader';
12+
import { REGIONS, type Region } from '../../config/schema';
1213
import { isInteractive } from '../../utils/env';
1314
import { maskToken } from '../../utils/token';
1415
import type { Config } from '../../config/schema';
@@ -76,26 +77,46 @@ export default defineCommand({
7677
);
7778
}
7879

79-
// Validate the key by calling quota endpoint
80+
// Validate key by probing all regions in parallel.
81+
// A CN key fails against the global endpoint (and vice versa), so we must
82+
// try every region to avoid false "validation failed" errors.
8083
if (!config.dryRun) {
8184
process.stderr.write('Testing key... ');
82-
try {
83-
const testConfig = { ...config, apiKey: key };
84-
await requestJson<QuotaResponse>(testConfig, {
85-
url: quotaEndpoint(testConfig.baseUrl),
86-
});
87-
process.stderr.write('Valid\n');
88-
} catch {
85+
86+
const regions = Object.keys(REGIONS) as Region[];
87+
const results = await Promise.all(
88+
regions.map(async (region) => {
89+
const baseUrl = REGIONS[region];
90+
try {
91+
const testConfig = { ...config, apiKey: key, baseUrl };
92+
await requestJson<QuotaResponse>(testConfig, {
93+
url: quotaEndpoint(baseUrl),
94+
});
95+
return { region, ok: true as const, error: '' };
96+
} catch (err) {
97+
return { region, ok: false as const, error: err instanceof Error ? err.message : String(err) };
98+
}
99+
}),
100+
);
101+
102+
const match = results.find((r) => r.ok);
103+
if (!match) {
104+
const details = results.map((r) => `${r.region}: ${r.error}`).join('; ');
89105
throw new CLIError(
90-
'API key validation failed.',
106+
`API key validation failed: ${details}`,
91107
ExitCode.AUTH,
92-
'Check that your key is valid and belongs to a Token Plan.',
108+
'Run with --verbose for request details.',
93109
);
94110
}
95111

96-
// Store key in config.json
112+
process.stderr.write(`Valid (${match.region})\n`);
113+
114+
config.region = match.region;
115+
config.baseUrl = REGIONS[match.region];
116+
97117
const existing = readConfigFile() as Record<string, unknown>;
98118
existing.api_key = key;
119+
existing.region = match.region;
99120
await writeConfigFile(existing);
100121
process.stderr.write(`API key saved to ${getConfigPath()}\n`);
101122

src/main.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,17 @@ async function main() {
9595
}
9696

9797
if (config.needsRegionDetection) {
98-
const apiKey = config.apiKey || config.fileApiKey;
99-
if (apiKey) {
100-
const detected = await detectRegion(apiKey);
101-
config.region = detected;
102-
config.baseUrl = REGIONS[detected];
103-
config.needsRegionDetection = false;
104-
await saveDetectedRegion(detected);
98+
// auth login handles its own region detection during key validation
99+
const isAuthLogin = commandPath[0] === 'auth' && commandPath[1] === 'login';
100+
if (!isAuthLogin) {
101+
const apiKey = config.apiKey || config.fileApiKey;
102+
if (apiKey) {
103+
const detected = await detectRegion(apiKey);
104+
config.region = detected;
105+
config.baseUrl = REGIONS[detected];
106+
config.needsRegionDetection = false;
107+
await saveDetectedRegion(detected);
108+
}
105109
}
106110
}
107111

0 commit comments

Comments
 (0)