Skip to content

Commit 2450eef

Browse files
committed
fix(auth): use region-specific OAuth endpoints for cn region (#85)
OAuth login always used the international platform URLs (platform.minimax.io) regardless of the user's configured region. This causes 404 errors for cn-region users during OAuth login. Changes: - src/auth/oauth.ts: Replace hardcoded DEFAULT_OAUTH_CONFIG with getOAuthConfig(region) that selects the correct endpoints for global vs cn - src/auth/refresh.ts: Use region-specific token refresh URL - src/auth/types.ts: Store region in CredentialFile for token refresh - src/commands/auth/login.ts: Pass region to OAuth flow and persist region in saved credentials
1 parent aff1e03 commit 2450eef

4 files changed

Lines changed: 44 additions & 17 deletions

File tree

src/auth/oauth.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { OAuthTokens } from './types';
2+
import type { Region } from '../config/schema';
23
import { CLIError } from '../errors/base';
34
import { ExitCode } from '../errors/codes';
45

@@ -12,17 +13,33 @@ export interface OAuthConfig {
1213
callbackPort: number;
1314
}
1415

15-
const DEFAULT_OAUTH_CONFIG: OAuthConfig = {
16-
clientId: 'mmx-cli',
17-
authorizationUrl: 'https://platform.minimax.io/oauth/authorize',
18-
tokenUrl: 'https://api.minimax.io/v1/oauth/token',
19-
deviceCodeUrl: 'https://api.minimax.io/v1/oauth/device/code',
20-
scopes: ['api'],
21-
callbackPort: 18991,
22-
};
16+
const OAUTH_ENDPOINTS = {
17+
global: {
18+
authorizationUrl: 'https://platform.minimax.io/oauth/authorize',
19+
tokenUrl: 'https://api.minimax.io/v1/oauth/token',
20+
deviceCodeUrl: 'https://api.minimax.io/v1/oauth/device/code',
21+
},
22+
cn: {
23+
authorizationUrl: 'https://platform.minimaxi.com/oauth/authorize',
24+
tokenUrl: 'https://api.minimaxi.com/v1/oauth/token',
25+
deviceCodeUrl: 'https://api.minimaxi.com/v1/oauth/device/code',
26+
},
27+
} as const;
28+
29+
export function getOAuthConfig(region: Region, options?: { callbackPort?: number }): OAuthConfig {
30+
const endpoints = OAUTH_ENDPOINTS[region];
31+
return {
32+
clientId: 'mmx-cli',
33+
authorizationUrl: endpoints.authorizationUrl,
34+
tokenUrl: endpoints.tokenUrl,
35+
deviceCodeUrl: endpoints.deviceCodeUrl,
36+
scopes: ['api'],
37+
callbackPort: options?.callbackPort ?? 18991,
38+
};
39+
}
2340

2441
export async function startBrowserFlow(
25-
config: OAuthConfig = DEFAULT_OAUTH_CONFIG,
42+
config: OAuthConfig = getOAuthConfig('global'),
2643
): Promise<OAuthTokens> {
2744
const { randomBytes, createHash } = await import('crypto');
2845
const codeVerifier = randomBytes(32).toString('base64url');
@@ -129,7 +146,7 @@ async function waitForCallback(port: number, expectedState: string): Promise<str
129146
}
130147

131148
export async function startDeviceCodeFlow(
132-
config: OAuthConfig = DEFAULT_OAUTH_CONFIG,
149+
config: OAuthConfig = getOAuthConfig('global'),
133150
): Promise<OAuthTokens> {
134151
// Request device code
135152
const codeRes = await fetch(config.deviceCodeUrl, {

src/auth/refresh.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import type { OAuthTokens, CredentialFile } from './types';
2+
import type { Region } from '../config/schema';
23
import { saveCredentials } from './credentials';
34
import { CLIError } from '../errors/base';
45
import { ExitCode } from '../errors/codes';
56

6-
// OAuth config — endpoints TBD pending MiniMax OAuth documentation
7-
const TOKEN_URL = 'https://api.minimax.io/v1/oauth/token';
7+
const TOKEN_URLS: Record<Region, string> = {
8+
global: 'https://api.minimax.io/v1/oauth/token',
9+
cn: 'https://api.minimaxi.com/v1/oauth/token',
10+
} as const;
811

912
export async function refreshAccessToken(
1013
refreshToken: string,
14+
region: Region = 'global',
1115
): Promise<OAuthTokens> {
12-
const res = await fetch(TOKEN_URL, {
16+
const tokenUrl = TOKEN_URLS[region];
17+
const res = await fetch(tokenUrl, {
1318
method: 'POST',
1419
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
1520
body: new URLSearchParams({
@@ -39,7 +44,8 @@ export async function ensureFreshToken(creds: CredentialFile): Promise<string> {
3944
}
4045

4146
// Token expired or about to expire — refresh
42-
const tokens = await refreshAccessToken(creds.refresh_token);
47+
const region = (creds.region as Region) || 'global';
48+
const tokens = await refreshAccessToken(creds.refresh_token, region);
4349

4450
const updated: CredentialFile = {
4551
access_token: tokens.access_token,

src/auth/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface CredentialFile {
1313
expires_at: string; // ISO 8601
1414
token_type: 'Bearer';
1515
account?: string;
16+
region?: string; // Region at time of login, used for token refresh URL
1617
}
1718

1819
export interface ResolvedCredential {

src/commands/auth/login.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { defineCommand } from '../../command';
22
import { CLIError } from '../../errors/base';
33
import { ExitCode } from '../../errors/codes';
44
import { saveCredentials } from '../../auth/credentials';
5-
import { startBrowserFlow, startDeviceCodeFlow } from '../../auth/oauth';
5+
import { startBrowserFlow, startDeviceCodeFlow, getOAuthConfig } from '../../auth/oauth';
66
import { requestJson } from '../../client/http';
77
import { quotaEndpoint } from '../../client/endpoints';
88
import { renderQuotaTable } from '../../output/quota-table';
@@ -112,18 +112,21 @@ export default defineCommand({
112112
return;
113113
}
114114

115+
const oauthConfig = getOAuthConfig(config.region);
116+
115117
let tokens;
116118
if (flags.noBrowser) {
117-
tokens = await startDeviceCodeFlow();
119+
tokens = await startDeviceCodeFlow(oauthConfig);
118120
} else {
119-
tokens = await startBrowserFlow();
121+
tokens = await startBrowserFlow(oauthConfig);
120122
}
121123

122124
const creds: CredentialFile = {
123125
access_token: tokens.access_token,
124126
refresh_token: tokens.refresh_token,
125127
expires_at: new Date(Date.now() + tokens.expires_in * 1000).toISOString(),
126128
token_type: 'Bearer',
129+
region: config.region,
127130
};
128131

129132
await saveCredentials(creds);

0 commit comments

Comments
 (0)