Skip to content

Commit 6035608

Browse files
committed
fix(gui): focus trap, Anthropic-specific ToS wording, provider-aware saferPath
OAuthTosWarningModal: add onKeyDown Tab-trap so keyboard users cannot escape the dialog. Anthropic gets its own body string distinguishing direct token reuse from supported Agent SDK integrations. saferPath (API key suggestion) is shown only for providers that support it (Anthropic, Google Antigravity), omitted for Copilot/Cursor. All four locales (en/ko/zh/de) updated. Test key list updated.
1 parent 44a393c commit 6035608

6 files changed

Lines changed: 49 additions & 16 deletions

File tree

gui/src/components/OAuthTosWarningModal.tsx

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ export default function OAuthTosWarningModal({
4646
// Unmarked provider: render nothing (callers must gate with oauthTosRisk).
4747
if (!level) return null;
4848

49+
const normalizedProviderId = providerId.trim().toLowerCase();
50+
const bodyKey = normalizedProviderId === "anthropic"
51+
? "oauthTos.anthropicBody"
52+
: oauthTosRiskBodyKey(level);
53+
const showApiKeySaferPath =
54+
normalizedProviderId === "anthropic"
55+
|| normalizedProviderId === "google-antigravity";
56+
4957
const handleContinue = () => {
5058
if (!acknowledged || submittedRef.current) return;
5159
submittedRef.current = true;
@@ -61,6 +69,24 @@ export default function OAuthTosWarningModal({
6169
className="modal-overlay"
6270
onCancel={handleCancel}
6371
onClick={onCancel}
72+
onKeyDown={e => {
73+
if (e.key !== "Tab") return;
74+
const dialog = dialogRef.current;
75+
if (!dialog) return;
76+
const focusable = dialog.querySelectorAll<HTMLElement>(
77+
"input:not([disabled]), button:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])",
78+
);
79+
if (focusable.length === 0) return;
80+
const first = focusable.item(0);
81+
const last = focusable.item(focusable.length - 1);
82+
if (e.shiftKey && document.activeElement === first) {
83+
e.preventDefault();
84+
last.focus();
85+
} else if (!e.shiftKey && document.activeElement === last) {
86+
e.preventDefault();
87+
first.focus();
88+
}
89+
}}
6490
>
6591
<div
6692
className="modal-card"
@@ -75,12 +101,14 @@ export default function OAuthTosWarningModal({
75101
>
76102
<IconAlert width={16} height={16} style={{ flexShrink: 0, marginTop: 2 }} aria-hidden="true" />
77103
<p className="modal-desc" style={{ margin: 0 }}>
78-
{t(oauthTosRiskBodyKey(level), { provider: providerLabel })}
104+
{t(bodyKey, { provider: providerLabel })}
79105
</p>
80106
</div>
81-
<p className="muted text-label" style={{ marginTop: 12 }}>
82-
{t("oauthTos.saferPath")}
83-
</p>
107+
{showApiKeySaferPath && (
108+
<p className="muted text-label" style={{ marginTop: 12 }}>
109+
{t("oauthTos.saferPath")}
110+
</p>
111+
)}
84112
<label className="oauth-tos-ack" style={{ display: "flex", gap: 8, alignItems: "flex-start", marginTop: 14 }}>
85113
<input
86114
type="checkbox"

gui/src/i18n/de.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,10 @@ export const de = {
147147
"prov.loginOk": "Bei {provider} angemeldet. Führe {cmd} aus (oder es gilt live), um seine Modelle aufzulisten.",
148148
"oauthTos.highTitle": "{provider}: Risiko bei Abo-OAuth",
149149
"oauthTos.elevatedTitle": "{provider}: inoffizielle OAuth-Brücke",
150-
"oauthTos.highBody": "OpenCodex ist ein Drittanbieter-Proxy. {provider} kann Abo-OAuth auf offizielle Apps oder genehmigte Integrationen beschränken. Die Weiterleitung von Tokens über nicht genehmigte Pfade kann gegen die Nutzungsbedingungen verstoßen und zu Limits oder Kontosperrung führen.",
151-
"oauthTos.elevatedBody": "OpenCodex verbindet {provider} über einen inoffiziellen OAuth-Pfad. Anbieter können ungewöhnlichen oder automatisierten Traffic als Missbrauch werten und den Zugang sperren. Bevorzuge den offiziellen Client oder einen API-Schlüssel.",
152-
"oauthTos.saferPath": "Sicherere Optionen: die offizielle App, eine genehmigte SDK-Integration oder einen API-Schlüssel verwenden.",
150+
"oauthTos.anthropicBody": "Die direkte Wiederverwendung von Claude-Abo-OAuth-Tokens über einen Drittanbieter-Proxy wie OpenCodex ist keine von Anthropic unterstützte Integration und kann zu Zugriffsbeschränkungen führen. Unterstützte Agent-SDK-Integrationen, die Claude-Abos verwenden, sind davon getrennt.",
151+
"oauthTos.highBody": "OpenCodex verbindet {provider} über einen OAuth-Pfad eines Drittanbieters. Bei nicht unterstützter Nutzung kann der Zugriff eingeschränkt oder gesperrt werden.",
152+
"oauthTos.elevatedBody": "OpenCodex verbindet {provider} über einen inoffiziellen OAuth-Pfad. Nutze nach Möglichkeit den offiziellen Client; ungewöhnlicher oder automatisierter Traffic kann als Missbrauch gewertet und der Zugriff eingeschränkt oder gesperrt werden.",
153+
"oauthTos.saferPath": "Sicherere Option: Hinterlege stattdessen einen API-Schlüssel in OpenCodex.",
153154
"oauthTos.acknowledge": "Ich verstehe das Risiko und möchte trotzdem mit OAuth fortfahren.",
154155
"oauthTos.continue": "Mit OAuth fortfahren",
155156
"prov.logoutOk": "Von {provider} abgemeldet.",

gui/src/i18n/en.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,10 @@ export const en = {
158158
"prov.loginOk": "Logged in to {provider}. Run {cmd} (or it applies live) to list its models.",
159159
"oauthTos.highTitle": "{provider}: subscription OAuth risk",
160160
"oauthTos.elevatedTitle": "{provider}: unofficial OAuth bridge",
161-
"oauthTos.highBody": "OpenCodex is a third-party proxy. {provider} may restrict subscription OAuth to official apps or approved integrations. Routing tokens through an unapproved path can violate their terms and may lead to rate limits or account suspension.",
162-
"oauthTos.elevatedBody": "OpenCodex bridges {provider} through an unofficial OAuth path. Providers may treat unusual or automated traffic as abuse and suspend access. Prefer an official client or an API key when available.",
163-
"oauthTos.saferPath": "Safer options: use the provider's official app, an approved SDK integration, or configure an API key when available.",
161+
"oauthTos.anthropicBody": "Directly reusing Claude subscription OAuth tokens through a third-party proxy such as OpenCodex is not a supported Anthropic integration and may lead to access restrictions. Supported Agent SDK integrations that use Claude subscriptions are separate.",
162+
"oauthTos.highBody": "OpenCodex connects {provider} through a third-party OAuth path. Unsupported use may lead to access limits or suspension.",
163+
"oauthTos.elevatedBody": "OpenCodex connects {provider} through an unofficial OAuth path. Use the official client when possible; unusual or automated traffic may be treated as abuse and access may be limited or suspended.",
164+
"oauthTos.saferPath": "Safer option: configure an API key in OpenCodex instead.",
164165
"oauthTos.acknowledge": "I understand the risk and want to continue with OAuth anyway.",
165166
"oauthTos.continue": "Continue with OAuth",
166167
"prov.logoutOk": "Logged out of {provider}.",

gui/src/i18n/ko.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,10 @@ export const ko: Record<TKey, string> = {
153153
"prov.loginOk": "{provider} 에 로그인했습니다. 모델을 표시하려면 {cmd} 를 실행하세요(또는 실시간 적용됩니다).",
154154
"oauthTos.highTitle": "{provider}: 구독 OAuth 위험",
155155
"oauthTos.elevatedTitle": "{provider}: 비공식 OAuth 브리지",
156-
"oauthTos.highBody": "OpenCodex는 타사 프록시입니다. {provider}는 구독 OAuth를 공식 앱이나 승인된 연동으로 제한할 수 있습니다. 비승인 경로로 토큰을 우회하면 약관 위반으로 제한이나 계정 정지가 발생할 수 있습니다.",
157-
"oauthTos.elevatedBody": "OpenCodex는 {provider}를 비공식 OAuth 경로로 연결합니다. 비정상/자동화 트래픽은 남용으로 간주되어 접근이 정지될 수 있습니다. 가능하면 공식 클라이언트나 API 키를 사용하세요.",
158-
"oauthTos.saferPath": "더 안전한 방법: 공식 앱, 승인된 SDK 연동, 또는 API 키를 사용하세요.",
156+
"oauthTos.anthropicBody": "Claude 구독 OAuth 토큰을 OpenCodex 같은 타사 프록시에서 직접 재사용하는 방식은 Anthropic이 지원하는 통합이 아니며 접근이 제한될 수 있습니다. Claude 구독을 사용하는 공식 Agent SDK 통합은 별도입니다.",
157+
"oauthTos.highBody": "OpenCodex는 {provider}를 타사 OAuth 경로로 연결합니다. 지원되지 않는 사용 방식이면 접근이 제한되거나 정지될 수 있습니다.",
158+
"oauthTos.elevatedBody": "OpenCodex는 {provider}를 비공식 OAuth 경로로 연결합니다. 가능하면 공식 클라이언트를 사용하세요. 비정상적이거나 자동화된 트래픽은 남용으로 간주되어 접근이 제한되거나 정지될 수 있습니다.",
159+
"oauthTos.saferPath": "더 안전한 방법: OpenCodex에 API 키를 대신 설정하세요.",
159160
"oauthTos.acknowledge": "위험을 이해했으며 OAuth로 계속 진행합니다.",
160161
"oauthTos.continue": "OAuth로 계속",
161162
"prov.logoutOk": "{provider} 에서 로그아웃했습니다.",

gui/src/i18n/zh.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,10 @@ export const zh: Record<TKey, string> = {
153153
"prov.loginOk": "已登录到 {provider}。运行 {cmd}(或实时生效)以列出其模型。",
154154
"oauthTos.highTitle": "{provider}:订阅 OAuth 风险",
155155
"oauthTos.elevatedTitle": "{provider}:非官方 OAuth 桥接",
156-
"oauthTos.highBody": "OpenCodex 是第三方代理。{provider} 可能将订阅 OAuth 限制在官方应用或已授权集成内。通过未授权路径转发令牌可能违反其条款,导致限流或账号停用。",
157-
"oauthTos.elevatedBody": "OpenCodex 通过非官方 OAuth 路径连接 {provider}。异常或自动化流量可能被判定为滥用并暂停访问。请优先使用官方客户端或 API 密钥。",
158-
"oauthTos.saferPath": "更安全的做法:使用官方应用、已授权的 SDK 集成,或配置 API 密钥。",
156+
"oauthTos.anthropicBody": "通过 OpenCodex 等第三方代理直接复用 Claude 订阅 OAuth 令牌,并非 Anthropic 支持的集成方式,可能导致访问受限。可使用 Claude 订阅的受支持 Agent SDK 集成属于另一种方式。",
157+
"oauthTos.highBody": "OpenCodex 通过第三方 OAuth 路径连接 {provider}。如果该用法不受支持,访问可能会被限制或暂停。",
158+
"oauthTos.elevatedBody": "OpenCodex 通过非官方 OAuth 路径连接 {provider}。请尽量使用官方客户端;异常或自动化流量可能被视为滥用,访问可能会被限制或暂停。",
159+
"oauthTos.saferPath": "更安全的做法:改为在 OpenCodex 中配置 API 密钥。",
159160
"oauthTos.acknowledge": "我了解风险,仍要继续使用 OAuth。",
160161
"oauthTos.continue": "继续使用 OAuth",
161162
"prov.logoutOk": "已退出 {provider}。",

tests/oauth-tos-warning.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ describe("oauth ToS warning UI seam", () => {
6565
const keys = [
6666
"oauthTos.highTitle",
6767
"oauthTos.elevatedTitle",
68+
"oauthTos.anthropicBody",
6869
"oauthTos.highBody",
6970
"oauthTos.elevatedBody",
7071
"oauthTos.saferPath",

0 commit comments

Comments
 (0)