diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eba353f4a..0a519f8a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Fixed +- fix(docs): correct the OAuth redirect URI in the Fly.io deployment guide. It told users to register `/api/oauth//callback`, but OmniRoute's browser OAuth flow uses a single `/callback` handler (there is no per-provider callback route). The mismatch caused GitLab Duo (and any OAuth provider) to reject the flow with "The redirect URI included is not valid". Added a regression guard test. ([#3732](https://github.com/diegosouzapw/OmniRoute/issues/3732)) + - fix(providers): give Ollama Cloud's `kimi-k2.7-code` its real capabilities (262K context, 262K max output, vision + thinking + tools) instead of the degraded `128000 / 8192` defaults. The model had no spec/registry entry, so importing it via "Import from /models" (whose `/v1/models` upstream returns no per-model metadata) left it as a bare custom model with fallback capabilities. Added a global `kimi-k2.7-code` model spec (parity with `kimi-k2.6`) plus a registry entry on `ollama-cloud`. ([#3761](https://github.com/diegosouzapw/OmniRoute/issues/3761) — thanks @SultanKs4) - fix(providers): repair qwen-web (chat.qwen.ai) connection validation, which failed with a misleading `provider.validation.ssrf_blocked` error. qwen-web had no specialty validator, so the generic OpenAI-compatible path probed a non-existent `/api/v2/models` URL that answers with a 307 redirect — the outbound guard blocked the redirect and the route mislabeled it as an SSRF security block. Added a `qwen-web` specialty validator that probes the real session endpoint (`GET /api/v2/user`, mirroring the executor's anti-bot headers + cookie-jar replay). Also hardened `toValidationErrorResult` so a blocked redirect is only flagged `securityBlocked` when its target is a private/internal host — a benign 3xx to a public host is no longer mislabeled as an SSRF attempt (this affected every web-cookie provider, not just qwen). ([#3288](https://github.com/diegosouzapw/OmniRoute/issues/3288), [#3758](https://github.com/diegosouzapw/OmniRoute/issues/3758)) diff --git a/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md b/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md index 7393824a9d..4454ff52e6 100644 --- a/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md +++ b/docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md @@ -206,16 +206,14 @@ flyctl deploy 2. **在 provider 控制台配置回调 URL (configure the callback URL on the provider console / configure a URL de callback no painel do provider)** - 通常格式为 (typical format / formato típico): + 所有 OAuth provider 共用同一个回调路径 `/callback`,不带 provider 段 (all OAuth providers share the single callback path `/callback` — there is NO per-provider callback route / todos os providers OAuth usam o mesmo callback `/callback`, sem segmento por provider): ```text - /api/oauth//callback + /callback ``` - 例如 (e.g. / p.ex.): - - `https://omniroute.fly.dev/api/oauth/gemini/callback` - - `https://omniroute.fly.dev/api/oauth/antigravity/callback` - - `https://omniroute.fly.dev/api/oauth/cursor/callback` + 例如 (e.g. / p.ex.),无论是 Gemini、Antigravity、Cursor 还是 GitLab Duo (regardless of Gemini / Antigravity / Cursor / GitLab Duo, etc.): + - `https://omniroute.fly.dev/callback` 如果 `NEXT_PUBLIC_BASE_URL` 与 provider 控制台中注册的回调 URL 不一致,OAuth 流程会在浏览器回跳阶段失败 (mismatch between `NEXT_PUBLIC_BASE_URL` and the registered callback URL will cause OAuth to fail at the browser redirect step / divergência entre `NEXT_PUBLIC_BASE_URL` e a URL de callback registrada quebra o OAuth no redirect do navegador)。 diff --git a/tests/unit/oauth-callback-path-doc.test.ts b/tests/unit/oauth-callback-path-doc.test.ts new file mode 100644 index 0000000000..ea1b3b4751 --- /dev/null +++ b/tests/unit/oauth-callback-path-doc.test.ts @@ -0,0 +1,47 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { resolve, join } from "node:path"; + +// Regression guard for #3732. Browser OAuth (GitLab Duo, Gemini, Antigravity, Cursor, …) +// completes at the single App Router page `/callback` — there is NO per-provider +// `/api/oauth//callback` route. `OAuthModal.startOAuthFlow` builds the +// redirect_uri as `/callback`, so a user who registers a different path in the +// provider console gets "The redirect URI included is not valid". Docs that told users +// to register `/api/oauth//callback` were wrong and caused #3732. +// These assertions lock the documented path to the real handler. + +const cwd = process.cwd(); + +test("the real browser OAuth callback handler lives at /callback", () => { + assert.ok( + existsSync(resolve(join(cwd, "src/app/callback/page.tsx"))), + "src/app/callback/page.tsx (the /callback handler) must exist" + ); + // There is no `callback` action in the [provider]/[action] OAuth route, so a + // `/api/oauth//callback` URL is NOT a real endpoint. + const oauthRoute = readFileSync( + resolve(join(cwd, "src/app/api/oauth/[provider]/[action]/route.ts")), + "utf8" + ); + assert.ok( + !/action === ["']callback["']/.test(oauthRoute), + "there must be no `callback` action in the OAuth [provider]/[action] route" + ); +}); + +test("the Fly.io deployment guide documents the correct /callback redirect URI (#3732)", () => { + const guide = readFileSync( + resolve(join(cwd, "docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md")), + "utf8" + ); + assert.ok( + !/\/api\/oauth\/[^/\s]+\/callback/.test(guide), + "the guide must not tell users to register a per-provider /api/oauth//callback URI" + ); + assert.match( + guide, + /\/callback/, + "the guide must document the real `/callback` redirect URI" + ); +});