mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-17 04:17:11 +00:00
fix(cliproxy): align kiro auth methods with upstream contracts
- add typed Kiro auth-method model and AWS device-code default - map CLI flags and callback behavior by selected Kiro method - harden CLI-only method guards and add contract regression tests Refs #552 Refs #233
This commit is contained in:
@@ -7,6 +7,74 @@
|
||||
import { CLIProxyProvider } from '../types';
|
||||
import { AccountInfo } from '../account-manager';
|
||||
|
||||
/**
|
||||
* Kiro authentication methods supported by CLIProxyAPIPlus.
|
||||
* - aws: AWS Builder ID via Device Code flow
|
||||
* - aws-authcode: AWS Builder ID via Authorization Code flow (CLI flag only)
|
||||
* - google: Social OAuth via Google
|
||||
* - github: Social OAuth via GitHub (management API only)
|
||||
*/
|
||||
export const KIRO_AUTH_METHODS = ['aws', 'aws-authcode', 'google', 'github'] as const;
|
||||
export type KiroAuthMethod = (typeof KIRO_AUTH_METHODS)[number];
|
||||
|
||||
/** CLI binary supports these Kiro methods directly via flags. */
|
||||
export const KIRO_CLI_AUTH_METHODS = ['aws', 'aws-authcode', 'google'] as const;
|
||||
export type KiroCLIAuthMethod = (typeof KIRO_CLI_AUTH_METHODS)[number];
|
||||
|
||||
/** Default Kiro method for CCS UX and AWS Organization support. */
|
||||
export const DEFAULT_KIRO_AUTH_METHOD: KiroAuthMethod = 'aws';
|
||||
|
||||
export function isKiroAuthMethod(value: string): value is KiroAuthMethod {
|
||||
return KIRO_AUTH_METHODS.includes(value as KiroAuthMethod);
|
||||
}
|
||||
|
||||
export function isKiroCLIAuthMethod(value: string): value is KiroCLIAuthMethod {
|
||||
return KIRO_CLI_AUTH_METHODS.includes(value as KiroCLIAuthMethod);
|
||||
}
|
||||
|
||||
export function normalizeKiroAuthMethod(value?: string): KiroAuthMethod {
|
||||
if (!value) return DEFAULT_KIRO_AUTH_METHOD;
|
||||
const normalized = value.trim().toLowerCase();
|
||||
return isKiroAuthMethod(normalized) ? normalized : DEFAULT_KIRO_AUTH_METHOD;
|
||||
}
|
||||
|
||||
export function isKiroDeviceCodeMethod(method: KiroAuthMethod): boolean {
|
||||
return method === 'aws';
|
||||
}
|
||||
|
||||
export function getKiroCallbackPort(method: KiroAuthMethod): number | null {
|
||||
return isKiroDeviceCodeMethod(method) ? null : 9876;
|
||||
}
|
||||
|
||||
export function getKiroCLIAuthFlag(method: KiroCLIAuthMethod): string {
|
||||
switch (method) {
|
||||
case 'aws':
|
||||
return '--kiro-aws-login';
|
||||
case 'aws-authcode':
|
||||
return '--kiro-aws-authcode';
|
||||
case 'google':
|
||||
return '--kiro-google-login';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kiro method for CLIProxyAPI management endpoint:
|
||||
* GET /v0/management/kiro-auth-url?method=<value>
|
||||
*/
|
||||
export function toKiroManagementMethod(method: KiroAuthMethod): 'aws' | 'google' | 'github' {
|
||||
switch (method) {
|
||||
case 'google':
|
||||
return 'google';
|
||||
case 'github':
|
||||
return 'github';
|
||||
case 'aws-authcode':
|
||||
return 'aws';
|
||||
case 'aws':
|
||||
default:
|
||||
return 'aws';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth callback ports used by CLIProxyAPI (hardcoded in binary)
|
||||
* See: https://github.com/router-for-me/CLIProxyAPI/tree/main/internal/auth
|
||||
@@ -113,7 +181,9 @@ export const OAUTH_CONFIGS: Record<CLIProxyProvider, ProviderOAuthConfig> = {
|
||||
displayName: 'Kiro (AWS)',
|
||||
authUrl: 'https://oidc.us-east-1.amazonaws.com',
|
||||
scopes: ['codewhisperer:completions', 'codewhisperer:conversations'],
|
||||
authFlag: '--kiro-login',
|
||||
// Default to AWS Builder ID device code flow for better compatibility.
|
||||
// Other Kiro methods are selected at runtime via OAuthOptions.kiroMethod.
|
||||
authFlag: '--kiro-aws-login',
|
||||
},
|
||||
ghcp: {
|
||||
provider: 'ghcp',
|
||||
@@ -213,6 +283,8 @@ export interface OAuthOptions {
|
||||
account?: string;
|
||||
add?: boolean;
|
||||
nickname?: string;
|
||||
/** Kiro auth method override (CLI + Dashboard parity). */
|
||||
kiroMethod?: KiroAuthMethod;
|
||||
/** If true, triggered from Web UI (enables project selection prompt) */
|
||||
fromUI?: boolean;
|
||||
/** If true, use --no-incognito flag (Kiro only - use normal browser instead of incognito) */
|
||||
|
||||
@@ -29,10 +29,15 @@ import {
|
||||
} from '../../management/oauth-port-diagnostics';
|
||||
import {
|
||||
OAuthOptions,
|
||||
OAUTH_CALLBACK_PORTS,
|
||||
DEFAULT_KIRO_AUTH_METHOD,
|
||||
getKiroCallbackPort,
|
||||
getKiroCLIAuthFlag,
|
||||
isKiroCLIAuthMethod,
|
||||
isKiroDeviceCodeMethod,
|
||||
getOAuthConfig,
|
||||
ProviderOAuthConfig,
|
||||
CLIPROXY_CALLBACK_PROVIDER_MAP,
|
||||
normalizeKiroAuthMethod,
|
||||
} from './auth-types';
|
||||
import { isHeadlessEnvironment, killProcessOnPort, showStep } from './environment-detector';
|
||||
import { getProviderTokenDir, isAuthenticated, registerAccountFromToken } from './token-manager';
|
||||
@@ -414,6 +419,8 @@ export async function triggerOAuth(
|
||||
const oauthConfig = getOAuthConfig(provider);
|
||||
const { verbose = false, add = false, fromUI = false, noIncognito = true } = options;
|
||||
let { nickname } = options;
|
||||
const resolvedKiroMethod =
|
||||
provider === 'kiro' ? normalizeKiroAuthMethod(options.kiroMethod) : DEFAULT_KIRO_AUTH_METHOD;
|
||||
|
||||
// Check for existing accounts
|
||||
const existingAccounts = getProviderAccounts(provider);
|
||||
@@ -444,10 +451,28 @@ export async function triggerOAuth(
|
||||
return null;
|
||||
}
|
||||
|
||||
const callbackPort = OAUTH_PORTS[provider];
|
||||
if (provider === 'kiro' && resolvedKiroMethod === 'github') {
|
||||
console.log(fail('Kiro GitHub login is only available in Dashboard management OAuth flow.'));
|
||||
console.log(' Use: ccs config -> Accounts -> Add Kiro account -> Method: GitHub OAuth');
|
||||
return null;
|
||||
}
|
||||
|
||||
const callbackPort =
|
||||
provider === 'kiro' ? getKiroCallbackPort(resolvedKiroMethod) : OAUTH_PORTS[provider];
|
||||
const isCLI = !fromUI;
|
||||
const headless = options.headless ?? isHeadlessEnvironment();
|
||||
const isDeviceCodeFlow = callbackPort === null;
|
||||
const isDeviceCodeFlow =
|
||||
provider === 'kiro' ? isKiroDeviceCodeMethod(resolvedKiroMethod) : callbackPort === null;
|
||||
|
||||
let authFlag = oauthConfig.authFlag;
|
||||
if (provider === 'kiro') {
|
||||
if (!isKiroCLIAuthMethod(resolvedKiroMethod)) {
|
||||
console.log(fail(`Kiro auth method '${resolvedKiroMethod}' is not supported by CLI flow.`));
|
||||
console.log(' Use Dashboard management OAuth for this method.');
|
||||
return null;
|
||||
}
|
||||
authFlag = getKiroCLIAuthFlag(resolvedKiroMethod);
|
||||
}
|
||||
|
||||
// Interactive mode selection for headless environments
|
||||
// Skip if explicit mode flag provided or device code flow (no callback needed)
|
||||
@@ -493,7 +518,7 @@ export async function triggerOAuth(
|
||||
const { binaryPath, tokenDir, configPath } = prepared;
|
||||
|
||||
// Free callback port if needed (only for authorization code flows)
|
||||
const localCallbackPort = OAUTH_CALLBACK_PORTS[provider];
|
||||
const localCallbackPort = callbackPort;
|
||||
if (localCallbackPort) {
|
||||
const killed = killProcessOnPort(localCallbackPort, verbose);
|
||||
if (killed && verbose) {
|
||||
@@ -502,7 +527,7 @@ export async function triggerOAuth(
|
||||
}
|
||||
|
||||
// Build args
|
||||
const args = ['--config', configPath, oauthConfig.authFlag];
|
||||
const args = ['--config', configPath, authFlag];
|
||||
if (headless) {
|
||||
args.push('--no-browser');
|
||||
}
|
||||
|
||||
@@ -12,7 +12,14 @@ import {
|
||||
OAUTH_CALLBACK_PORTS as DIAGNOSTIC_CALLBACK_PORTS,
|
||||
OAUTH_FLOW_TYPES,
|
||||
} from '../../../src/management/oauth-port-diagnostics';
|
||||
import { OAUTH_CALLBACK_PORTS as AUTH_CALLBACK_PORTS } from '../../../src/cliproxy/auth/auth-types';
|
||||
import {
|
||||
DEFAULT_KIRO_AUTH_METHOD,
|
||||
getKiroCallbackPort,
|
||||
getKiroCLIAuthFlag,
|
||||
normalizeKiroAuthMethod,
|
||||
OAUTH_CALLBACK_PORTS as AUTH_CALLBACK_PORTS,
|
||||
toKiroManagementMethod,
|
||||
} from '../../../src/cliproxy/auth/auth-types';
|
||||
|
||||
describe('provider-capabilities', () => {
|
||||
it('keeps canonical provider IDs backward-compatible', () => {
|
||||
@@ -75,4 +82,25 @@ describe('provider-capabilities', () => {
|
||||
expect(AUTH_CALLBACK_PORTS[provider]).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('maps Kiro auth methods to upstream CLI/management contracts', () => {
|
||||
expect(DEFAULT_KIRO_AUTH_METHOD).toBe('aws');
|
||||
expect(normalizeKiroAuthMethod()).toBe('aws');
|
||||
expect(normalizeKiroAuthMethod('GOOGLE')).toBe('google');
|
||||
expect(normalizeKiroAuthMethod('not-valid')).toBe('aws');
|
||||
|
||||
expect(getKiroCLIAuthFlag('aws')).toBe('--kiro-aws-login');
|
||||
expect(getKiroCLIAuthFlag('aws-authcode')).toBe('--kiro-aws-authcode');
|
||||
expect(getKiroCLIAuthFlag('google')).toBe('--kiro-google-login');
|
||||
|
||||
expect(getKiroCallbackPort('aws')).toBeNull();
|
||||
expect(getKiroCallbackPort('google')).toBe(9876);
|
||||
expect(getKiroCallbackPort('github')).toBe(9876);
|
||||
expect(getKiroCallbackPort('aws-authcode')).toBe(9876);
|
||||
|
||||
expect(toKiroManagementMethod('aws')).toBe('aws');
|
||||
expect(toKiroManagementMethod('aws-authcode')).toBe('aws');
|
||||
expect(toKiroManagementMethod('google')).toBe('google');
|
||||
expect(toKiroManagementMethod('github')).toBe('github');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user