Skip to content

Add proxy support for add account by token#90

Merged
dwgx merged 1 commit intodwgx:masterfrom
smeinecke:feat/add-account-proxy
Apr 29, 2026
Merged

Add proxy support for add account by token#90
dwgx merged 1 commit intodwgx:masterfrom
smeinecke:feat/add-account-proxy

Conversation

@smeinecke
Copy link
Copy Markdown
Contributor

改了什么 / What changed

Added an optional proxy field to the "Add Account" form in the Account Pool dashboard. Users can now specify a proxy (in protocol://[user:pass@]host:port format) when adding an account via API Key or Auth Token, instead of having to configure it separately after the account is created.

为什么 / Why

Previously, when adding an account to the pool, users had to:

  1. Add the account first (which would use the global proxy)
  2. Then separately configure a per-account proxy after creation

This was inconvenient, especially when managing multiple accounts with different proxies. The batch import feature already supported specifying proxies inline, but the single account addition form did not. This change brings feature parity between batch import and single account addition.

测试 / Testing

  • Tested proxy validation:

    • Invalid format (e.g., not-a-proxy) returns ERR_PROXY_FORMAT_INVALID
    • Private IP (e.g., http://192.168.1.1:8080) is blocked by default (returns ERR_PROXY_PRIVATE_IP)
    • Private IP allowed when ALLOW_PRIVATE_PROXY_HOSTS=1 is set
    • Valid public proxy (e.g., http://proxy.example.com:8080) works correctly
    • SOCKS5 with auth (e.g., socks5://user:pass@host:port) parses correctly
  • Tested dashboard UI:

    • Proxy field accepts input and clears after successful account addition
    • Empty proxy field omits proxy from request (uses global proxy)
    • i18n translations display correctly in both English and Chinese
  • Verified proxy is applied immediately:

    • Account is created → proxy is set → LS instance is spawned for the proxy

Checklist

  • 代码风格和现有文件一致 / Code style matches existing files
  • 没有引入 npm 依赖 / No new npm dependencies (project is zero-dep)
  • 涉及 LS binary 协议改动时 在 PR 描述里注明字段号来源 / If touching LS protocol, document field-number source in the PR description (N/A - no LS protocol changes)
  • 涉及 dashboard UI 用 App.confirm / App.prompt 不用浏览器原生 alert/confirm / Uses App.confirm / App.prompt, not native dialogs (if dashboard)

dwgx added a commit that referenced this pull request Apr 29, 2026
Merging — thanks @smeinecke. The fail-closed default (`ALLOW_PRIVATE_PROXY_HOSTS=` empty) preserves SSRF protection for public deployments while unblocking local/private-network testing for opt-in users.

Follow-ups for v2.0.27 (will track in repo, no action needed from you):
- Add a `test/ssrf.test.js` covering: default-OFF rejects 192.168/10.x/localhost, opt-in `ALLOW_PRIVATE_PROXY_HOSTS=1` allows them, IPv6 link-local still rejected.
- Doc note: this switch should NOT be enabled on public-facing dashboards.
- Will rebase #90 on top of this once you fix the account-create-before-proxy-validation ordering (see comments there).
Copy link
Copy Markdown
Owner

@dwgx dwgx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

非常感谢 @smeinecke — 这个功能用户呼声不小(#84 #87 都隐含相关诉求),方向完全对。但合并前有一处 数据完整性 bug 需要先修。

阻塞点:账号先建后校验代理 → 失败留下僵尸账号

当前 src/dashboard/api.js 大约 +295 行的逻辑:

// 这里 addAccountByKey/addAccountByToken 已经把账号写进 store 了
} else {
  return json(res, 400, { error: 'Provide api_key or token' });
}

// 然后才校验 proxy
if (body.proxy) {
  const parsed = parseProxyUrl(body.proxy);
  if (!parsed) {
    return json(res, 400, { error: 'ERR_PROXY_FORMAT_INVALID' });  // ← 账号已存在,但接口返回 400
  }
  if (config.allowPrivateProxyHosts) {
    await validateHostFormat(parsed.host);  // ← 同上,throw 后账号还在
  } else {
    await assertPublicUrlHost(parsed.host);  // ← 同上
  }
  setAccountProxy(account.id, parsed);
  ensureLsForAccount(account.id).catch(...);
}

用户视角:贴 token + 写错 proxy → dashboard 报 400 → 用户以为没添加成功 → 重试 → 重复账号 / 已经吃掉 quota / 配额异常。

建议的修法(顺序倒过来)

// 1. 先解析 + 校验 proxy(如果传了),失败直接 400,不碰 account store
let parsedProxy = null;
if (body.proxy) {
  parsedProxy = parseProxyUrl(body.proxy);
  if (!parsedProxy) return json(res, 400, { error: 'ERR_PROXY_FORMAT_INVALID' });
  if (config.allowPrivateProxyHosts) {
    await validateHostFormat(parsedProxy.host);
  } else {
    await assertPublicUrlHost(parsedProxy.host);
  }
}

// 2. 然后再创建账号
let account;
if (body.api_key) account = await addAccountByKey(...);
else if (body.token) account = await addAccountByToken(...);
else return json(res, 400, { error: 'Provide api_key or token' });

// 3. 最后绑定 proxy
if (parsedProxy) {
  setAccountProxy(account.id, parsedProxy);
  ensureLsForAccount(account.id).catch(e => log.warn(`LS ensure failed: ${e.message}`));
}

顺手建议

  1. 加个测试 test/account-add-proxy.test.js:bad proxy → 0 accounts;good proxy → 1 account + proxy bound;no proxy → 1 account no proxy。
  2. 这个 PR 把 #88ALLOW_PRIVATE_PROXY_HOSTS 改动复带进来了。我刚 merge 了 #88,请 rebase 一下,把重复的 .env.example / setup.sh / config.js / net-safety.js / 文档改动删掉,只留 add-account 相关的 diff 即可。
  3. App.addAccount() 失败分支目前还 toast 原始 r.error,可以改用统一的 App.translateError(r.error) helper(#89 引入的 error.${code} 模式)— 不过这个不是 blocker,可以另开 PR。

修好这一处 + rebase 完,我立刻 merge。再次感谢 🙏

@smeinecke
Copy link
Copy Markdown
Contributor Author

You're right. I'll fix this and rebase the branch. Thank you for the feedback!

@dwgx
Copy link
Copy Markdown
Owner

dwgx commented Apr 29, 2026

Oh, I'm very sorry I didn't realize just now that you are not Chinese. I used Chinese. Thank you for your Pull

Add optional proxy input to dashboard single-account add form. Mirrors batch-import proxy binding — user can specify http://proxy:8080 or socks5://user:pass@host:port per account. Backend validates format via parseProxyUrl(), respects ALLOW_PRIVATE_PROXY_HOSTS config (validateHostFormat vs assertPublicUrlHost), calls setAccountProxy() + ensureLsForAccount(). UI clears proxy field on success. i18n for EN/zh-CN.
@smeinecke smeinecke force-pushed the feat/add-account-proxy branch from 072d151 to a4dc792 Compare April 29, 2026 09:14
@dwgx dwgx merged commit 59c5f71 into dwgx:master Apr 29, 2026
1 of 2 checks passed
dwgx added a commit that referenced this pull request Apr 29, 2026
#91 — 从本地 Windsurf 桌面客户端读凭证:
- 新增 src/dashboard/local-windsurf.js:扫 state.vscdb (sqlite) 抽 windsurfAuthStatus + sessions
- 新增 GET /accounts/import-local:req.socket.remoteAddress 严格 loopback 校验
- 默认 UI 加「从本地 Windsurf 导入」按钮 + 列表展示 + 一键导入
- 用 node:sqlite 零依赖,Node 20 graceful 降级到 ~/.codeium/config.json fallback

#90 follow-up — proxy 校验顺序修正:
- /accounts POST 把 proxy 解析+校验提到 addAccount 之前,避免失败留僵尸账号
- 新增 4 条 test/account-add-proxy-ordering.test.js 锁住

测试:327/327 passing(+16 新);i18n guard ✓
smeinecke added a commit to smeinecke/WindsurfAPI that referenced this pull request Apr 29, 2026
…ounts

Move api_key/token presence check to top of POST /accounts handler - runs before proxy validation + account creation. Prevents proxy network checks when credentials are missing. Simplify account creation to ternary expression (api_key vs token path). No functional change to validation order from dwgx#90 (proxy still validated before account creation).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants