feat: add Kiro OAuth login support
- Add Kiro OAuth card to OAuthPage with AWS Builder ID and IDC login - Support refreshToken import from Kiro IDE - Add kiro.svg icon - Add i18n translations for zh-CN and en
This commit is contained in:
4
src/assets/icons/kiro.svg
Normal file
4
src/assets/icons/kiro.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" fill="#FF9900"/>
|
||||
<path d="M12 6L8 10h3v4H8l4 4 4-4h-3v-4h3l-4-4z" fill="#232F3E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 244 B |
@@ -675,7 +675,26 @@
|
||||
"iflow_cookie_result_expired": "Expires At",
|
||||
"iflow_cookie_result_path": "Saved Path",
|
||||
"iflow_cookie_result_type": "Type",
|
||||
"remote_access_disabled": "This login method is not available for remote access. Please access from localhost."
|
||||
"remote_access_disabled": "This login method is not available for remote access. Please access from localhost.",
|
||||
"kiro_oauth_title": "Kiro OAuth",
|
||||
"kiro_oauth_hint": "Login to Kiro service via AWS SSO, supporting Builder ID and Identity Center (IDC), or import refreshToken from Kiro IDE directly.",
|
||||
"kiro_builder_id_label": "AWS Builder ID Login",
|
||||
"kiro_builder_id_hint": "Login with AWS Builder ID account, suitable for individual developers.",
|
||||
"kiro_builder_id_button": "Login with Builder ID",
|
||||
"kiro_idc_label": "AWS Identity Center (IDC) Login",
|
||||
"kiro_idc_hint": "Login with enterprise AWS Identity Center, requires Start URL and Region.",
|
||||
"kiro_idc_start_url_label": "Start URL",
|
||||
"kiro_idc_start_url_placeholder": "https://your-org.awsapps.com/start",
|
||||
"kiro_idc_region_label": "Region (Optional)",
|
||||
"kiro_idc_region_placeholder": "us-east-1",
|
||||
"kiro_idc_button": "Login with IDC",
|
||||
"kiro_token_import_label": "Token Import",
|
||||
"kiro_token_import_hint": "Import refreshToken from Kiro IDE, can be found in Kiro IDE's auth file.",
|
||||
"kiro_token_placeholder": "Paste refreshToken",
|
||||
"kiro_token_import_button": "Import Token",
|
||||
"kiro_token_required": "Please enter refreshToken first",
|
||||
"kiro_token_import_success": "Kiro Token imported successfully",
|
||||
"kiro_token_import_error": "Kiro Token import failed:"
|
||||
},
|
||||
"usage_stats": {
|
||||
"title": "Usage Statistics",
|
||||
|
||||
@@ -675,7 +675,26 @@
|
||||
"iflow_cookie_result_expired": "过期时间",
|
||||
"iflow_cookie_result_path": "保存路径",
|
||||
"iflow_cookie_result_type": "类型",
|
||||
"remote_access_disabled": "远程访问不支持此登录方式,请从本地 (localhost) 访问"
|
||||
"remote_access_disabled": "远程访问不支持此登录方式,请从本地 (localhost) 访问",
|
||||
"kiro_oauth_title": "Kiro OAuth",
|
||||
"kiro_oauth_hint": "通过 AWS SSO 登录 Kiro 服务,支持 Builder ID 和 Identity Center (IDC) 两种方式,或直接导入 Kiro IDE 的 refreshToken。",
|
||||
"kiro_builder_id_label": "AWS Builder ID 登录",
|
||||
"kiro_builder_id_hint": "使用 AWS Builder ID 账号登录,适用于个人开发者。",
|
||||
"kiro_builder_id_button": "使用 Builder ID 登录",
|
||||
"kiro_idc_label": "AWS Identity Center (IDC) 登录",
|
||||
"kiro_idc_hint": "使用企业 AWS Identity Center 登录,需要提供 Start URL 和 Region。",
|
||||
"kiro_idc_start_url_label": "Start URL",
|
||||
"kiro_idc_start_url_placeholder": "https://your-org.awsapps.com/start",
|
||||
"kiro_idc_region_label": "Region (可选)",
|
||||
"kiro_idc_region_placeholder": "us-east-1",
|
||||
"kiro_idc_button": "使用 IDC 登录",
|
||||
"kiro_token_import_label": "Token 导入",
|
||||
"kiro_token_import_hint": "从 Kiro IDE 导入 refreshToken,可在 Kiro IDE 的认证文件中找到。",
|
||||
"kiro_token_placeholder": "粘贴 refreshToken",
|
||||
"kiro_token_import_button": "导入 Token",
|
||||
"kiro_token_required": "请先填写 refreshToken",
|
||||
"kiro_token_import_success": "Kiro Token 导入成功",
|
||||
"kiro_token_import_error": "Kiro Token 导入失败:"
|
||||
},
|
||||
"usage_stats": {
|
||||
"title": "使用统计",
|
||||
|
||||
@@ -15,6 +15,7 @@ import iconGemini from '@/assets/icons/gemini.svg';
|
||||
import iconQwen from '@/assets/icons/qwen.svg';
|
||||
import iconIflow from '@/assets/icons/iflow.svg';
|
||||
import iconVertex from '@/assets/icons/vertex.svg';
|
||||
import iconKiro from '@/assets/icons/kiro.svg';
|
||||
|
||||
interface ProviderState {
|
||||
url?: string;
|
||||
@@ -54,6 +55,19 @@ interface VertexImportState {
|
||||
result?: VertexImportResult;
|
||||
}
|
||||
|
||||
interface KiroOAuthState {
|
||||
method: 'builder-id' | 'idc' | null;
|
||||
startUrl: string;
|
||||
region: string;
|
||||
}
|
||||
|
||||
interface KiroTokenImportState {
|
||||
token: string;
|
||||
loading: boolean;
|
||||
error?: string;
|
||||
success?: boolean;
|
||||
}
|
||||
|
||||
const PROVIDERS: { id: OAuthProvider; titleKey: string; hintKey: string; urlLabelKey: string; icon: string | { light: string; dark: string } }[] = [
|
||||
{ id: 'codex', titleKey: 'auth_login.codex_oauth_title', hintKey: 'auth_login.codex_oauth_hint', urlLabelKey: 'auth_login.codex_oauth_url_label', icon: { light: iconCodexLight, dark: iconCodexDark } },
|
||||
{ id: 'anthropic', titleKey: 'auth_login.anthropic_oauth_title', hintKey: 'auth_login.anthropic_oauth_hint', urlLabelKey: 'auth_login.anthropic_oauth_url_label', icon: iconClaude },
|
||||
@@ -82,6 +96,15 @@ export function OAuthPage() {
|
||||
location: '',
|
||||
loading: false
|
||||
});
|
||||
const [kiroOAuth, setKiroOAuth] = useState<KiroOAuthState>({
|
||||
method: null,
|
||||
startUrl: '',
|
||||
region: ''
|
||||
});
|
||||
const [kiroTokenImport, setKiroTokenImport] = useState<KiroTokenImportState>({
|
||||
token: '',
|
||||
loading: false
|
||||
});
|
||||
const timers = useRef<Record<string, number>>({});
|
||||
const vertexFileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
@@ -303,6 +326,48 @@ export function OAuthPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const openKiroOAuth = (method: 'builder-id' | 'idc') => {
|
||||
const baseUrl = window.location.origin;
|
||||
let url = `${baseUrl}/v0/oauth/kiro/start?method=${method}`;
|
||||
if (method === 'idc') {
|
||||
const startUrl = kiroOAuth.startUrl.trim();
|
||||
const region = kiroOAuth.region.trim();
|
||||
if (startUrl) {
|
||||
url += `&start_url=${encodeURIComponent(startUrl)}`;
|
||||
}
|
||||
if (region) {
|
||||
url += `®ion=${encodeURIComponent(region)}`;
|
||||
}
|
||||
}
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
const handleKiroTokenImport = async () => {
|
||||
const token = kiroTokenImport.token.trim();
|
||||
if (!token) {
|
||||
showNotification(t('auth_login.kiro_token_required'), 'warning');
|
||||
return;
|
||||
}
|
||||
setKiroTokenImport((prev) => ({ ...prev, loading: true, error: undefined, success: undefined }));
|
||||
try {
|
||||
const baseUrl = window.location.origin;
|
||||
const response = await fetch(`${baseUrl}/v0/oauth/kiro/import`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ token })
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.error || `HTTP ${response.status}`);
|
||||
}
|
||||
setKiroTokenImport((prev) => ({ ...prev, loading: false, success: true }));
|
||||
showNotification(t('auth_login.kiro_token_import_success'), 'success');
|
||||
} catch (err: any) {
|
||||
setKiroTokenImport((prev) => ({ ...prev, loading: false, error: err?.message }));
|
||||
showNotification(`${t('auth_login.kiro_token_import_error')} ${err?.message || ''}`, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<h1 className={styles.pageTitle}>{t('nav.oauth', { defaultValue: 'OAuth' })}</h1>
|
||||
@@ -574,6 +639,95 @@ export function OAuthPage() {
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Kiro OAuth 登录 */}
|
||||
<Card
|
||||
title={
|
||||
<span className={styles.cardTitle}>
|
||||
<img src={iconKiro} alt="" className={styles.cardTitleIcon} />
|
||||
{t('auth_login.kiro_oauth_title')}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div className="hint">{t('auth_login.kiro_oauth_hint')}</div>
|
||||
|
||||
{/* AWS Builder ID 登录 */}
|
||||
<div className="form-group" style={{ marginTop: 16 }}>
|
||||
<label className="label">{t('auth_login.kiro_builder_id_label')}</label>
|
||||
<div className="hint">{t('auth_login.kiro_builder_id_hint')}</div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => openKiroOAuth('builder-id')}
|
||||
style={{ marginTop: 8 }}
|
||||
>
|
||||
{t('auth_login.kiro_builder_id_button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* AWS Identity Center (IDC) 登录 */}
|
||||
<div className="form-group" style={{ marginTop: 16 }}>
|
||||
<label className="label">{t('auth_login.kiro_idc_label')}</label>
|
||||
<div className="hint">{t('auth_login.kiro_idc_hint')}</div>
|
||||
<Input
|
||||
label={t('auth_login.kiro_idc_start_url_label')}
|
||||
value={kiroOAuth.startUrl}
|
||||
onChange={(e) => setKiroOAuth((prev) => ({ ...prev, startUrl: e.target.value }))}
|
||||
placeholder={t('auth_login.kiro_idc_start_url_placeholder')}
|
||||
/>
|
||||
<Input
|
||||
label={t('auth_login.kiro_idc_region_label')}
|
||||
value={kiroOAuth.region}
|
||||
onChange={(e) => setKiroOAuth((prev) => ({ ...prev, region: e.target.value }))}
|
||||
placeholder={t('auth_login.kiro_idc_region_placeholder')}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={() => openKiroOAuth('idc')}
|
||||
style={{ marginTop: 8 }}
|
||||
>
|
||||
{t('auth_login.kiro_idc_button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Token 导入 */}
|
||||
<div className="form-group" style={{ marginTop: 16 }}>
|
||||
<label className="label">{t('auth_login.kiro_token_import_label')}</label>
|
||||
<div className="hint">{t('auth_login.kiro_token_import_hint')}</div>
|
||||
<Input
|
||||
value={kiroTokenImport.token}
|
||||
onChange={(e) =>
|
||||
setKiroTokenImport((prev) => ({
|
||||
...prev,
|
||||
token: e.target.value,
|
||||
error: undefined,
|
||||
success: undefined
|
||||
}))
|
||||
}
|
||||
placeholder={t('auth_login.kiro_token_placeholder')}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={handleKiroTokenImport}
|
||||
loading={kiroTokenImport.loading}
|
||||
style={{ marginTop: 8 }}
|
||||
>
|
||||
{t('auth_login.kiro_token_import_button')}
|
||||
</Button>
|
||||
{kiroTokenImport.success && (
|
||||
<div className="status-badge success" style={{ marginTop: 8 }}>
|
||||
{t('auth_login.kiro_token_import_success')}
|
||||
</div>
|
||||
)}
|
||||
{kiroTokenImport.error && (
|
||||
<div className="status-badge error" style={{ marginTop: 8 }}>
|
||||
{t('auth_login.kiro_token_import_error')} {kiroTokenImport.error}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user