mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-05-27 17:23:52 +00:00
chore(v3.8.4): consolidate pending fixes and roll version back from 3.8.5
Squashes multiple in-flight changes pending release into release/v3.8.4 since the in-progress 3.8.5 has been consolidated back into 3.8.4. CRITICAL — oauth/codex (multi-account regression revert) Revert the proactive expired-flip that #2743 (multi-agent review) added to open-sse/executors/base.ts. The new behaviour marked accounts as testStatus:"expired" + isActive:false from inside the PROACTIVE refresh path whenever isUnrecoverableRefreshError() fired — including transient sentinels (refresh_token_reused that the rotation map can recover, generic invalid_request blips). On multi-account Codex it sequentially disabled working accounts in the DB before any upstream call confirmed the failure. Keep the classification — that part is legitimate (avoids spreading the sentinel into activeCredentials and sending a non-token upstream). Drop only the DB mutation: the REACTIVE path in chatCore.ts:~3912 still flips the account to expired after the upstream confirms the auth failure, which is the correct moment (by then the rotation map at tokenRefresh.ts:~1541 and the DB-staleness check have already had their chance to recover). Marked the block "SOURCE OF TRUTH — do not flip the proactive path back. Ask the operator first." with the regression history (ad3d4b696->0c94c397d-> this revert) so a future review does not re-introduce the regression on autopilot. oauth/kiro — centralize social-flow constants in KIRO_CONFIG social-authorize/route.ts and social-exchange/route.ts duplicated the AWS Kiro device-auth URL and the "kiro-cli" public client identifier. Move both to KIRO_CONFIG (alongside the existing AWS SSO OIDC + social auth fields) and add an env override on socialClientId so operators can pin a custom value via KIRO_OAUTH_CLIENT_ID. New KIRO_CONFIG fields: socialClientId (env-overridable), socialDeviceAuthorizeUrl, socialDevicePollUrl. tests/unit/oauth-kiro.test.ts locks the contract: routes must import KIRO_CONFIG and must not inline the AWS URL or "kiro-cli" literal. dashboard/providers — memoize ProviderCard lookup constants Move KIND_LABEL and DOT_COLORS into useMemo so they don't recreate on every render. Functional parity, slightly cheaper re-renders. test(authz) — lockdown Next.js 16 proxy.ts contract New tests/unit/authz/proxy-contract.test.ts asserts the file lives at src/proxy.ts (not src/middleware.ts), exports the proxy function, delegates to runAuthzPipeline with enforce:true, and the matcher covers every prefix mounted under /api so unauthenticated requests cannot bypass the centralized tier checks. version — roll back from 3.8.5 to 3.8.4 CHANGELOG.md consolidates the unreleased 3.8.5 entries into the 3.8.4 section. Mirror that in package.json, package-lock.json and docs/reference/openapi.yaml. .source/* picked up the regenerated fumadocs section ordering. docs — env contract additions Add KIRO_OAUTH_CLIENT_ID and OMNIROUTE_PROXY_FETCH_DEBUG to .env.example and docs/reference/ENVIRONMENT.md so the env-doc-sync check stays green.
This commit is contained in:
parent
bcc2427f62
commit
448b65af2c
15 changed files with 350 additions and 128 deletions
10
.env.example
10
.env.example
|
|
@ -132,6 +132,11 @@ OMNIROUTE_USE_TURBOPACK=1
|
|||
# Used by: src/lib/credentialHealth/scheduler.ts
|
||||
# OMNIROUTE_DISABLE_CREDENTIAL_HEALTH_CHECK=false
|
||||
|
||||
# Set to "true" to emit `[ProxyFetch]` debug logs from the Vercel relay path
|
||||
# in open-sse/utils/proxyFetch.ts. Off by default to avoid leaking routing
|
||||
# hints in production logs.
|
||||
# OMNIROUTE_PROXY_FETCH_DEBUG=true
|
||||
|
||||
# Docker production port mappings (docker-compose.prod.yml only).
|
||||
# These set the HOST-side published ports. Container ports use PORT/API_PORT.
|
||||
# PROD_DASHBOARD_PORT=20130
|
||||
|
|
@ -666,6 +671,11 @@ CODEX_USER_AGENT="codex-cli/0.132.0 (Windows 10.0.26200; x64)"
|
|||
GITHUB_USER_AGENT="GitHubCopilotChat/0.45.1"
|
||||
ANTIGRAVITY_USER_AGENT="antigravity/2.0.1 linux/arm64 google-api-nodejs-client/10.3.0"
|
||||
KIRO_USER_AGENT="AWS-SDK-JS/3.0.0 kiro-ide/1.0.0"
|
||||
# Optional override for the Kiro social device-code OAuth clientId. Kiro's
|
||||
# device endpoint accepts any non-empty string and behaves like a User-Agent
|
||||
# rather than a secret. Only override if AWS ever starts enforcing this field.
|
||||
# Used by: src/lib/oauth/constants/oauth.ts (KIRO_CONFIG.socialClientId).
|
||||
# KIRO_OAUTH_CLIENT_ID=kiro-cli
|
||||
QODER_USER_AGENT="Qoder-Cli"
|
||||
QWEN_USER_AGENT="QwenCode/0.15.11 (linux; x64)"
|
||||
CURSOR_USER_AGENT="Cursor/3.4"
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -4,9 +4,9 @@ import { default as __fd_glob_63 } from "../docs/routing/meta.json?collection=do
|
|||
import { default as __fd_glob_62 } from "../docs/reference/openapi.yaml?collection=docs"
|
||||
import { default as __fd_glob_61 } from "../docs/reference/meta.json?collection=docs"
|
||||
import { default as __fd_glob_60 } from "../docs/ops/meta.json?collection=docs"
|
||||
import { default as __fd_glob_59 } from "../docs/guides/meta.json?collection=docs"
|
||||
import { default as __fd_glob_58 } from "../docs/frameworks/meta.json?collection=docs"
|
||||
import { default as __fd_glob_57 } from "../docs/compression/meta.json?collection=docs"
|
||||
import { default as __fd_glob_59 } from "../docs/compression/meta.json?collection=docs"
|
||||
import { default as __fd_glob_58 } from "../docs/guides/meta.json?collection=docs"
|
||||
import { default as __fd_glob_57 } from "../docs/frameworks/meta.json?collection=docs"
|
||||
import { default as __fd_glob_56 } from "../docs/architecture/meta.json?collection=docs"
|
||||
import { default as __fd_glob_55 } from "../docs/meta.json?collection=docs"
|
||||
import * as __fd_glob_54 from "../docs/security/STEALTH_GUIDE.md?collection=docs"
|
||||
|
|
@ -32,28 +32,28 @@ import * as __fd_glob_35 from "../docs/ops/PROXY_GUIDE.md?collection=docs"
|
|||
import * as __fd_glob_34 from "../docs/ops/FLY_IO_DEPLOYMENT_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_33 from "../docs/ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md?collection=docs"
|
||||
import * as __fd_glob_32 from "../docs/ops/COVERAGE_PLAN.md?collection=docs"
|
||||
import * as __fd_glob_31 from "../docs/guides/USER_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_30 from "../docs/guides/UNINSTALL.md?collection=docs"
|
||||
import * as __fd_glob_29 from "../docs/guides/TROUBLESHOOTING.md?collection=docs"
|
||||
import * as __fd_glob_28 from "../docs/guides/TERMUX_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_27 from "../docs/guides/SETUP_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_26 from "../docs/guides/PWA_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_25 from "../docs/guides/KIRO_SETUP.md?collection=docs"
|
||||
import * as __fd_glob_24 from "../docs/guides/I18N.md?collection=docs"
|
||||
import * as __fd_glob_23 from "../docs/guides/FEATURES.md?collection=docs"
|
||||
import * as __fd_glob_22 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_21 from "../docs/guides/DOCKER_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_20 from "../docs/frameworks/WEBHOOKS.md?collection=docs"
|
||||
import * as __fd_glob_19 from "../docs/frameworks/SKILLS.md?collection=docs"
|
||||
import * as __fd_glob_18 from "../docs/frameworks/OPENCODE.md?collection=docs"
|
||||
import * as __fd_glob_17 from "../docs/frameworks/MEMORY.md?collection=docs"
|
||||
import * as __fd_glob_16 from "../docs/frameworks/MCP-SERVER.md?collection=docs"
|
||||
import * as __fd_glob_15 from "../docs/frameworks/GAMIFICATION.md?collection=docs"
|
||||
import * as __fd_glob_14 from "../docs/frameworks/EVALS.md?collection=docs"
|
||||
import * as __fd_glob_13 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"
|
||||
import * as __fd_glob_12 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs"
|
||||
import * as __fd_glob_11 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_10 from "../docs/frameworks/A2A-SERVER.md?collection=docs"
|
||||
import * as __fd_glob_31 from "../docs/frameworks/WEBHOOKS.md?collection=docs"
|
||||
import * as __fd_glob_30 from "../docs/frameworks/SKILLS.md?collection=docs"
|
||||
import * as __fd_glob_29 from "../docs/frameworks/OPENCODE.md?collection=docs"
|
||||
import * as __fd_glob_28 from "../docs/frameworks/MEMORY.md?collection=docs"
|
||||
import * as __fd_glob_27 from "../docs/frameworks/MCP-SERVER.md?collection=docs"
|
||||
import * as __fd_glob_26 from "../docs/frameworks/GAMIFICATION.md?collection=docs"
|
||||
import * as __fd_glob_25 from "../docs/frameworks/EVALS.md?collection=docs"
|
||||
import * as __fd_glob_24 from "../docs/frameworks/EMBEDDED-SERVICES.md?collection=docs"
|
||||
import * as __fd_glob_23 from "../docs/frameworks/CLOUD_AGENT.md?collection=docs"
|
||||
import * as __fd_glob_22 from "../docs/frameworks/AGENT_PROTOCOLS_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_21 from "../docs/frameworks/A2A-SERVER.md?collection=docs"
|
||||
import * as __fd_glob_20 from "../docs/guides/USER_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_19 from "../docs/guides/UNINSTALL.md?collection=docs"
|
||||
import * as __fd_glob_18 from "../docs/guides/TROUBLESHOOTING.md?collection=docs"
|
||||
import * as __fd_glob_17 from "../docs/guides/TERMUX_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_16 from "../docs/guides/SETUP_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_15 from "../docs/guides/PWA_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_14 from "../docs/guides/KIRO_SETUP.md?collection=docs"
|
||||
import * as __fd_glob_13 from "../docs/guides/I18N.md?collection=docs"
|
||||
import * as __fd_glob_12 from "../docs/guides/FEATURES.md?collection=docs"
|
||||
import * as __fd_glob_11 from "../docs/guides/ELECTRON_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_10 from "../docs/guides/DOCKER_GUIDE.md?collection=docs"
|
||||
import * as __fd_glob_9 from "../docs/compression/RTK_COMPRESSION.md?collection=docs"
|
||||
import * as __fd_glob_8 from "../docs/compression/COMPRESSION_RULES_FORMAT.md?collection=docs"
|
||||
import * as __fd_glob_7 from "../docs/compression/COMPRESSION_LANGUAGE_PACKS.md?collection=docs"
|
||||
|
|
@ -72,4 +72,4 @@ const create = server<typeof Config, import("fumadocs-mdx/runtime/types").Intern
|
|||
}
|
||||
}>({"doc":{"passthroughs":["extractedReferences"]}});
|
||||
|
||||
export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_55, "architecture/meta.json": __fd_glob_56, "compression/meta.json": __fd_glob_57, "frameworks/meta.json": __fd_glob_58, "guides/meta.json": __fd_glob_59, "ops/meta.json": __fd_glob_60, "reference/meta.json": __fd_glob_61, "reference/openapi.yaml": __fd_glob_62, "routing/meta.json": __fd_glob_63, "security/meta.json": __fd_glob_64, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/REPOSITORY_MAP.md": __fd_glob_3, "architecture/RESILIENCE_GUIDE.md": __fd_glob_4, "compression/COMPRESSION_ENGINES.md": __fd_glob_5, "compression/COMPRESSION_GUIDE.md": __fd_glob_6, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_7, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_8, "compression/RTK_COMPRESSION.md": __fd_glob_9, "frameworks/A2A-SERVER.md": __fd_glob_10, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_11, "frameworks/CLOUD_AGENT.md": __fd_glob_12, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_13, "frameworks/EVALS.md": __fd_glob_14, "frameworks/GAMIFICATION.md": __fd_glob_15, "frameworks/MCP-SERVER.md": __fd_glob_16, "frameworks/MEMORY.md": __fd_glob_17, "frameworks/OPENCODE.md": __fd_glob_18, "frameworks/SKILLS.md": __fd_glob_19, "frameworks/WEBHOOKS.md": __fd_glob_20, "guides/DOCKER_GUIDE.md": __fd_glob_21, "guides/ELECTRON_GUIDE.md": __fd_glob_22, "guides/FEATURES.md": __fd_glob_23, "guides/I18N.md": __fd_glob_24, "guides/KIRO_SETUP.md": __fd_glob_25, "guides/PWA_GUIDE.md": __fd_glob_26, "guides/SETUP_GUIDE.md": __fd_glob_27, "guides/TERMUX_GUIDE.md": __fd_glob_28, "guides/TROUBLESHOOTING.md": __fd_glob_29, "guides/UNINSTALL.md": __fd_glob_30, "guides/USER_GUIDE.md": __fd_glob_31, "ops/COVERAGE_PLAN.md": __fd_glob_32, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_33, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_34, "ops/PROXY_GUIDE.md": __fd_glob_35, "ops/RELEASE_CHECKLIST.md": __fd_glob_36, "ops/SQLITE_RUNTIME.md": __fd_glob_37, "ops/TUNNELS_GUIDE.md": __fd_glob_38, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_39, "reference/API_REFERENCE.md": __fd_glob_40, "reference/CLI-TOOLS.md": __fd_glob_41, "reference/ENVIRONMENT.md": __fd_glob_42, "reference/FREE_TIERS.md": __fd_glob_43, "reference/PROVIDER_REFERENCE.md": __fd_glob_44, "routing/AUTO-COMBO.md": __fd_glob_45, "routing/REASONING_REPLAY.md": __fd_glob_46, "security/CLI_TOKEN.md": __fd_glob_47, "security/CLI_TOKEN_AUTH.md": __fd_glob_48, "security/COMPLIANCE.md": __fd_glob_49, "security/ERROR_SANITIZATION.md": __fd_glob_50, "security/GUARDRAILS.md": __fd_glob_51, "security/PUBLIC_CREDS.md": __fd_glob_52, "security/ROUTE_GUARD_TIERS.md": __fd_glob_53, "security/STEALTH_GUIDE.md": __fd_glob_54, });
|
||||
export const docs = await create.docs("docs", "docs", {"meta.json": __fd_glob_55, "architecture/meta.json": __fd_glob_56, "frameworks/meta.json": __fd_glob_57, "guides/meta.json": __fd_glob_58, "compression/meta.json": __fd_glob_59, "ops/meta.json": __fd_glob_60, "reference/meta.json": __fd_glob_61, "reference/openapi.yaml": __fd_glob_62, "routing/meta.json": __fd_glob_63, "security/meta.json": __fd_glob_64, }, {"architecture/ARCHITECTURE.md": __fd_glob_0, "architecture/AUTHZ_GUIDE.md": __fd_glob_1, "architecture/CODEBASE_DOCUMENTATION.md": __fd_glob_2, "architecture/REPOSITORY_MAP.md": __fd_glob_3, "architecture/RESILIENCE_GUIDE.md": __fd_glob_4, "compression/COMPRESSION_ENGINES.md": __fd_glob_5, "compression/COMPRESSION_GUIDE.md": __fd_glob_6, "compression/COMPRESSION_LANGUAGE_PACKS.md": __fd_glob_7, "compression/COMPRESSION_RULES_FORMAT.md": __fd_glob_8, "compression/RTK_COMPRESSION.md": __fd_glob_9, "guides/DOCKER_GUIDE.md": __fd_glob_10, "guides/ELECTRON_GUIDE.md": __fd_glob_11, "guides/FEATURES.md": __fd_glob_12, "guides/I18N.md": __fd_glob_13, "guides/KIRO_SETUP.md": __fd_glob_14, "guides/PWA_GUIDE.md": __fd_glob_15, "guides/SETUP_GUIDE.md": __fd_glob_16, "guides/TERMUX_GUIDE.md": __fd_glob_17, "guides/TROUBLESHOOTING.md": __fd_glob_18, "guides/UNINSTALL.md": __fd_glob_19, "guides/USER_GUIDE.md": __fd_glob_20, "frameworks/A2A-SERVER.md": __fd_glob_21, "frameworks/AGENT_PROTOCOLS_GUIDE.md": __fd_glob_22, "frameworks/CLOUD_AGENT.md": __fd_glob_23, "frameworks/EMBEDDED-SERVICES.md": __fd_glob_24, "frameworks/EVALS.md": __fd_glob_25, "frameworks/GAMIFICATION.md": __fd_glob_26, "frameworks/MCP-SERVER.md": __fd_glob_27, "frameworks/MEMORY.md": __fd_glob_28, "frameworks/OPENCODE.md": __fd_glob_29, "frameworks/SKILLS.md": __fd_glob_30, "frameworks/WEBHOOKS.md": __fd_glob_31, "ops/COVERAGE_PLAN.md": __fd_glob_32, "ops/E2E_DASHBOARD_SHAKEDOWN_v3.8.0.md": __fd_glob_33, "ops/FLY_IO_DEPLOYMENT_GUIDE.md": __fd_glob_34, "ops/PROXY_GUIDE.md": __fd_glob_35, "ops/RELEASE_CHECKLIST.md": __fd_glob_36, "ops/SQLITE_RUNTIME.md": __fd_glob_37, "ops/TUNNELS_GUIDE.md": __fd_glob_38, "ops/VM_DEPLOYMENT_GUIDE.md": __fd_glob_39, "reference/API_REFERENCE.md": __fd_glob_40, "reference/CLI-TOOLS.md": __fd_glob_41, "reference/ENVIRONMENT.md": __fd_glob_42, "reference/FREE_TIERS.md": __fd_glob_43, "reference/PROVIDER_REFERENCE.md": __fd_glob_44, "routing/AUTO-COMBO.md": __fd_glob_45, "routing/REASONING_REPLAY.md": __fd_glob_46, "security/CLI_TOKEN.md": __fd_glob_47, "security/CLI_TOKEN_AUTH.md": __fd_glob_48, "security/COMPLIANCE.md": __fd_glob_49, "security/ERROR_SANITIZATION.md": __fd_glob_50, "security/GUARDRAILS.md": __fd_glob_51, "security/PUBLIC_CREDS.md": __fd_glob_52, "security/ROUTE_GUARD_TIERS.md": __fd_glob_53, "security/STEALTH_GUIDE.md": __fd_glob_54, });
|
||||
82
CHANGELOG.md
82
CHANGELOG.md
|
|
@ -2,54 +2,12 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.8.5] — 2026-05-26
|
||||
## [3.8.4] — 2026-05-26
|
||||
|
||||
### 🔒 Security
|
||||
|
||||
- **authz:** redirect `/home` and `/home/:path*` to `/login` when unauthenticated — Next.js middleware matcher omitted `/home`, so any visit reached the page directly on `REQUIRE_LOGIN` deployments (#2712)
|
||||
- **review:** resolve v3.8.4 important + minor findings from consolidated review including SSRF guards (#2749)
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **mcp:** break callLogs ↔ compliance ESM cycle that deadlocks the bundled MCP server on Node.js 24 — extract no-log state to `compliance/noLog.ts`, switch callers to the leaf module, keep `compliance/index.ts` re-exports for backwards compat (#2650)
|
||||
- **deepseek:** guard PoW solver Web Worker handler so `require()` no longer throws `ReferenceError: onmessage is not defined` under Node strict mode (#2724)
|
||||
- **combos:** include no-auth providers (FreeAIAPIKey, BluesMinds, FreeModel.dev, opencode, …) in the combo builder picker — they were invisible because they never get rows in `provider_connections` (#2737)
|
||||
- **translator:** allow the `web_search` server-tool family (`web_search_20250305`, `web_search_20250101`, plain `web_search`) in the Responses API translator and preserve the original versioned name on output (#2695)
|
||||
- **oauth:** register the missing `trae` provider with `import_token` flow so the Trae IDE no longer 500s during token import (#2658)
|
||||
- **model:** merge settings-based aliases with the legacy DB alias namespace so aliases set via the Settings UI (e.g. `gpt-5.4 → cx/gpt-5.4`) are honored instead of being overridden by provider inference (#2618, #2208)
|
||||
- **kiro:** fall back to `document.execCommand("copy")` when the Clipboard API is unavailable (HTTP/non-secure contexts), so the "Copy authorization link" button works on LAN deployments (#2689)
|
||||
- **cli:** raise `omniroute serve` ready timeout from 20s to 60s and add a TCP-listening fallback so Windows users no longer get phantom timeouts during slow Next.js cold start (#2460)
|
||||
- **mcp:** break circular await deadlock in compliance→callLogs + Kiro refresh resilience (#2747)
|
||||
- **ui:** claude-web provider shows 'API Key' label instead of 'Session Cookie' (#2744)
|
||||
- **deepseek-web:** lazy start session refresh (#2742)
|
||||
- **docker:** keep fumadocs doc assets in Docker build context (#2741)
|
||||
- **vision-bridge:** force bridge for opencode-go/zen models that overstate vision support (#2740)
|
||||
- **combos:** enable universal handoff by default to preserve cross-model conversation context (#2736)
|
||||
|
||||
### 🏆 Hall de Contribuidores
|
||||
|
||||
Um agradecimento especial a todos que contribuíram com código, revisões e testes para este release:
|
||||
@diegosouzapw, @disonjer, @herjarsa, @janeza2, @oyi77, @thanet-s
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
- **proxy:** serverless relay endpoints with rate limiting (#2734)
|
||||
- **pwa:** enhanced manifest + push notification support (#2733)
|
||||
- **auth:** API key groups with model-level permissions (#2732)
|
||||
- **playground:** combo routing visual simulator (#2731)
|
||||
- **resilience:** credential health check + adaptive circuit breaker (#2730)
|
||||
|
||||
### ♻️ Refactors
|
||||
|
||||
- **openapi:** api endpoints audit (#2729)
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **db:** hotfix migration version collision (068_services + 068_webhooks_kind_metadata) (#2727)
|
||||
|
||||
## [3.8.4] — 2026-05-25
|
||||
- **authz:** redirect `/home` and `/home/:path*` to `/login` when unauthenticated — Next.js middleware matcher omitted `/home`, so any visit reached the page directly on `REQUIRE_LOGIN` deployments (#2712 — thanks @diegosouzapw)
|
||||
- **review:** resolve v3.8.4 important + minor findings from consolidated review including SSRF guards (#2749 — thanks @diegosouzapw)
|
||||
|
||||
### ✨ New Features
|
||||
|
||||
|
|
@ -71,6 +29,11 @@ Um agradecimento especial a todos que contribuíram com código, revisões e tes
|
|||
- **feat(sla):** SLA dashboard with uptime/latency/error rate queries, summary/trend APIs, uptime badges and sparklines
|
||||
- **feat(routing-analytics):** AI-powered usage pattern analysis and routing recommendations — combo_metrics queries, hourly failure heatmap, provider breakdown, cost-vs-latency scatter chart
|
||||
- **feat(teams):** fixed team execution with 13 git worktrees and project-level team configs
|
||||
- **feat(providers):** add Inner.ai provider support with native executor, translation support, and model catalog definitions (#2704 — thanks @df4p)
|
||||
- **feat(proxy):** unified free proxy pool, Vercel Relay serverless endpoints, and a redesigned 4-tab proxy dashboard interface (#2705 — thanks @diegosouzapw)
|
||||
- **feat(webhooks):** 3-step configuration wizard for Slack, Telegram, Discord, and Custom webhook destinations, with reorganized React components (#2703 — thanks @diegosouzapw)
|
||||
- **feat(openapi):** comprehensive API endpoints content audit with 100% schema coverage, authz security tiers, and full i18n localization support (#2701 — thanks @diegosouzapw)
|
||||
- **feat(providers):** add BluesMinds, FreeModel.dev, and FreeAIAPIKey to the provider catalog (#2709 — thanks @oyi77)
|
||||
- **chore(deps):** added ws + @types/ws for WebSocket support, recharts ^3.8.1 for analytics charts
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
|
@ -78,6 +41,30 @@ Um agradecimento especial a todos que contribuíram com código, revisões e tes
|
|||
- Fix combo cascade skipping on credential check timeout
|
||||
- Fix team sessions going idle (worktree initialization)
|
||||
- **feat(providers):** enhance Google Gemini, CLI, and Antigravity resilience and features — introduces explicit TypeScript typing to translation layers, adds new Gemini 2.0 models, implements backoff and retry logic in the Gemini CLI executor, extracts Google Search grounding metadata into standard `citations`, and adds backend definitions for the `vertex-partner` provider. ([#2676](https://github.com/diegosouzapw/OmniRoute/pull/2676) — thanks @alltomatos)
|
||||
- **fix(proxy):** atomically save and assign custom dashboard proxies in a single SQLite transaction, preventing orphan configuration rows (#2697 — thanks @terence71-glitch)
|
||||
- **fix(reasoning):** inject thinking blocks into Claude-format messages for Kimi K2 to prevent infinite tool-calling loops (#2699 — thanks @herjarsa)
|
||||
- **fix(antigravity):** default exhausted quota status display to 0% instead of 100% (#2700 — thanks @ahmet-cetinkaya)
|
||||
- **fix(electron):** add Caps Lock indicator, custom reset warnings, and suppress shell window spawning on startup (#2714 — thanks @benzntech)
|
||||
- **fix(combos):** resolve context handoff tags ordering issue and enforce a 60-second request timeout limit per combo target to prevent capacity leaks (#2717 — thanks @herjarsa)
|
||||
- **fix(oauth):** resolve parallel token refresh race conditions in Codex and implement comprehensive error checking across OAuth providers (#2718 — thanks @diegosouzapw)
|
||||
- **fix(docker):** install `python3`, `make`, and `g++` in the Docker builder stage to support native Node.js addon compilation (#2713 — thanks @mrmm)
|
||||
- **fix(i18n):** restore real hint and placeholder translation strings for web-cookie providers in `en.json` (#2694 — thanks @diegosouzapw)
|
||||
- **fix(db):** resolve migration version prefix collision between services and webhook metadata tables (#2727 — thanks @diegosouzapw)
|
||||
- **fix(vision-bridge):** ensure images are processed when a vision-capable model is matched through a combo routing mapping (#2706 — thanks @herjarsa)
|
||||
- **mcp:** break callLogs ↔ compliance ESM cycle that deadlocks the bundled MCP server on Node.js 24 — extract no-log state to `compliance/noLog.ts`, switch callers to the leaf module, keep `compliance/index.ts` re-exports for backwards compat (#2650 — thanks @disonjer)
|
||||
- **deepseek:** guard PoW solver Web Worker handler so `require()` no longer throws `ReferenceError: onmessage is not defined` under Node strict mode (#2724 — thanks @thanet-s)
|
||||
- **combos:** include no-auth providers (FreeAIAPIKey, BluesMinds, FreeModel.dev, opencode, …) in the combo builder picker — they were invisible because they never get rows in `provider_connections` (#2737 — thanks @herjarsa)
|
||||
- **translator:** allow the `web_search` server-tool family (`web_search_20250305`, `web_search_20250101`, plain `web_search`) in the Responses API translator and preserve the original versioned name on output (#2695 — thanks @diegosouzapw)
|
||||
- **oauth:** register the missing `trae` provider with `import_token` flow so the Trae IDE no longer 500s during token import (#2658 — thanks @diegosouzapw)
|
||||
- **model:** merge settings-based aliases with the legacy DB alias namespace so aliases set via the Settings UI (e.g. `gpt-5.4 → cx/gpt-5.4`) are honored instead of being overridden by provider inference (#2618, #2208 — thanks @diegosouzapw)
|
||||
- **kiro:** fall back to `document.execCommand("copy")` when the Clipboard API is unavailable (HTTP/non-secure contexts), so the "Copy authorization link" button works on LAN deployments (#2689 — thanks @disonjer)
|
||||
- **cli:** raise `omniroute serve` ready timeout from 20s to 60s and add a TCP-listening fallback so Windows users no longer get phantom timeouts during slow Next.js cold start (#2460 — thanks @benzntech)
|
||||
- **mcp:** break circular await deadlock in compliance→callLogs + Kiro refresh resilience (#2747 — thanks @disonjer)
|
||||
- **ui:** claude-web provider shows 'API Key' label instead of 'Session Cookie' (#2744 — thanks @oyi77)
|
||||
- **deepseek-web:** lazy start session refresh (#2742 — thanks @thanet-s)
|
||||
- **docker:** keep fumadocs doc assets in Docker build context (#2741 — thanks @janeza2)
|
||||
- **vision-bridge:** force bridge for opencode-go/zen models that overstate vision support (#2740 — thanks @herjarsa)
|
||||
- **combos:** enable universal handoff by default to preserve cross-model conversation context (#2736 — thanks @herjarsa)
|
||||
|
||||
### 🚀 Embedded Services
|
||||
|
||||
|
|
@ -95,6 +82,11 @@ Um agradecimento especial a todos que contribuíram com código, revisões e tes
|
|||
- **DB migration 071** (originally 068, renumbered post-merge to avoid collision with `068_free_proxies` and `068_webhooks_kind_metadata`) — extends `version_manager` table with `autoStart`, `autoUpdate`, `providerExpose`, `apiKey`, and `port` columns. `migrationRunner.ts` now throws at boot if two `.sql` files share the same numeric prefix.
|
||||
- All service routes classified as `LOCAL_ONLY` in `routeGuard.ts`; loopback enforcement is unconditional before any auth check (leaked JWT via tunnel cannot trigger process spawning).
|
||||
|
||||
### 🏆 Hall de Contribuidores
|
||||
|
||||
Um agradecimento especial a todos que contribuíram com código, revisões e testes para este release:
|
||||
@ahmet-cetinkaya, @alltomatos, @benzntech, @Chewji9875, @df4p, @diegosouzapw, @disonjer, @herjarsa, @janeza2, @mrmm, @oyi77, @thanet-s, @terence71-glitch
|
||||
|
||||
---
|
||||
|
||||
## [3.8.3] — 2026-05-24
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
|
|||
| `OMNIROUTE_MIGRATIONS_DIR` | _(auto-detect)_ | `src/lib/db/migrationRunner.ts` | Override the directory that the migration runner scans. Useful when shipping bundled migrations in custom builds. |
|
||||
| `OMNIROUTE_SPEND_FLUSH_INTERVAL_MS` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Flush interval (ms) for the batched spend/cost writer. Lower values reduce write coalescing; higher values reduce DB contention. |
|
||||
| `OMNIROUTE_SPEND_MAX_BUFFER_SIZE` | _(default in code)_ | `src/lib/spend/batchWriter.ts` | Max buffered spend entries before a forced flush. Raise on high-QPS deployments; lower when bounded memory matters more. |
|
||||
| `OMNIROUTE_PROXY_FETCH_DEBUG` | _(unset)_ | `open-sse/utils/proxyFetch.ts` | Set to `"true"` to emit `[ProxyFetch]` debug logs on the Vercel relay path. Off by default to avoid leaking routing hints. |
|
||||
| `BATCH_RETRY_DURATION_MS` | `86400000` (24h) | `open-sse/services/batchProcessor.ts` | Maximum retry window for individual batch items (ms). Items exceeding this duration are marked failed. |
|
||||
| `BATCH_BACKOFF_BASE_MS` | `5000` | `open-sse/services/batchProcessor.ts` | Base delay (ms) for exponential backoff on batch item retries. |
|
||||
| `BATCH_BACKOFF_MAX_MS` | `3600000` (1h) | `open-sse/services/batchProcessor.ts` | Cap (ms) for exponential backoff between batch item retries. |
|
||||
|
|
@ -430,6 +431,7 @@ process.env[`${PROVIDER_ID}_USER_AGENT`]
|
|||
| `GITHUB_USER_AGENT` | `GitHubCopilotChat/0.45.1` | When GitHub Copilot Chat updates |
|
||||
| `ANTIGRAVITY_USER_AGENT` | `antigravity/2.0.1 darwin/arm64` | When Antigravity IDE updates |
|
||||
| `KIRO_USER_AGENT` | `AWS-SDK-JS/3.0.0 kiro-ide/1.0.0` | When Kiro IDE updates |
|
||||
| `KIRO_OAUTH_CLIENT_ID` | `kiro-cli` | Override the Kiro social device-code `clientId` (public id) |
|
||||
| `QODER_USER_AGENT` | `Qoder-Cli` | When Qoder CLI updates |
|
||||
| `QWEN_USER_AGENT` | `QwenCode/0.15.9 (linux; x64)` | When Qwen Code updates |
|
||||
| `CURSOR_USER_AGENT` | `Cursor/3.3` | When Cursor updates |
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
openapi: 3.1.0
|
||||
info:
|
||||
title: OmniRoute API
|
||||
version: 3.8.5
|
||||
version: 3.8.4
|
||||
description: |
|
||||
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
|
||||
endpoint that routes requests to multiple AI providers with load balancing,
|
||||
|
|
|
|||
|
|
@ -590,25 +590,47 @@ export class BaseExecutor {
|
|||
);
|
||||
|
||||
if (refreshed && !proactivePersistRan) {
|
||||
// Classify unrecoverable sentinels before spreading. Without this guard
|
||||
// an { error: "unrecoverable_refresh_error", code } object passes the
|
||||
// truthy check and pollutes activeCredentials with garbage, causing the
|
||||
// executor to send a non-token to upstream. Mirrors the reactive path
|
||||
// in chatCore.ts and keeps the proactive path's behaviour consistent.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// ⚠️ SOURCE OF TRUTH — do not flip the proactive path back to
|
||||
// "persist expired+inactive". Ask the operator first.
|
||||
//
|
||||
// History (do not repeat past regressions):
|
||||
// - ad3d4b696 (#2718, 2026-05-25): per-connection mutex + onPersist
|
||||
// wiring so multi-account Codex (rotating refresh tokens) stops
|
||||
// hitting refresh_token_reused under concurrent load.
|
||||
// - 0c94c397d (#2743, 2026-05-26): a multi-agent review added a
|
||||
// `await onCredentialsRefreshed({ testStatus: "expired",
|
||||
// isActive: false })` here. That BROKE multi-account Codex —
|
||||
// transient sentinels (refresh_token_reused recoverable via
|
||||
// rotation map; generic invalid_request blips) were treated as
|
||||
// terminal, so the proactive path sequentially disabled
|
||||
// working accounts in the DB before any upstream call confirmed
|
||||
// the failure. Reverted intentionally.
|
||||
//
|
||||
// Contract for the PROACTIVE refresh path:
|
||||
// - Classify the sentinel ONLY to avoid spreading it into
|
||||
// activeCredentials (which would send a non-token upstream).
|
||||
// - DO NOT persist `{ testStatus: "expired", isActive: false }`
|
||||
// from here. That decision belongs to the REACTIVE path in
|
||||
// open-sse/handlers/chatCore.ts:~3912, which runs AFTER the
|
||||
// upstream confirmed the auth failure. By then the rotation
|
||||
// map (tokenRefresh.ts:~1541) and the DB-staleness check have
|
||||
// already had their chance to recover the request.
|
||||
//
|
||||
// If a future review/agent thinks the expired-flip is "missing"
|
||||
// here, STOP — flipping it here re-introduces the multi-account
|
||||
// Codex regression. Discuss with the operator before touching.
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
if (isUnrecoverableRefreshError(refreshed)) {
|
||||
const refreshCode = (refreshed as Record<string, unknown>).code;
|
||||
log?.error?.(
|
||||
log?.warn?.(
|
||||
"TOKEN",
|
||||
`${this.provider.toUpperCase()} | unrecoverable refresh — marking account expired (code=${String(refreshCode ?? "unknown")})`
|
||||
`${this.provider.toUpperCase()} | proactive refresh returned unrecoverable sentinel (code=${String(refreshCode ?? "unknown")}); keeping stale credentials, deferring to reactive path.`
|
||||
);
|
||||
if (onCredentialsRefreshed) {
|
||||
await onCredentialsRefreshed({
|
||||
testStatus: "expired",
|
||||
isActive: false,
|
||||
});
|
||||
}
|
||||
// Don't spread the sentinel — keep stale credentials so the next
|
||||
// upstream call surfaces the real auth error to the client.
|
||||
// Intentionally NOT spreading the sentinel and NOT persisting
|
||||
// expired status. The next upstream call either succeeds (rotation
|
||||
// map / DB-staleness saved us) or fails — chatCore.ts then marks
|
||||
// the account expired with confidence.
|
||||
} else {
|
||||
activeCredentials = {
|
||||
...credentials,
|
||||
|
|
|
|||
25
package-lock.json
generated
25
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.8.5",
|
||||
"version": "3.8.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "3.8.5",
|
||||
"version": "3.8.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
|
|
@ -90,6 +90,7 @@
|
|||
"@testing-library/react": "^16.3.2",
|
||||
"@types/bcryptjs": "^3.0.0",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/bun": "latest",
|
||||
"@types/keytar": "^4.4.2",
|
||||
"@types/node": "^25.9.1",
|
||||
"@types/react": "^19.2.15",
|
||||
|
|
@ -5277,6 +5278,16 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bun": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.14.tgz",
|
||||
"integrity": "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bun-types": "1.3.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/chai": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||
|
|
@ -7360,6 +7371,16 @@
|
|||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bun-types": {
|
||||
"version": "1.3.14",
|
||||
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.14.tgz",
|
||||
"integrity": "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/bundle-name": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "omniroute",
|
||||
"version": "3.8.5",
|
||||
"version": "3.8.4",
|
||||
"description": "Unified AI router with 160+ providers, RTK+Caveman compression, auto fallback, MCP/A2A, desktop, PWA, and OpenAI-compatible APIs.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
|
|
@ -259,7 +259,12 @@
|
|||
"postcss": "^8.5.14",
|
||||
"ip-address": "10.2.0",
|
||||
"qs": "^6.15.2",
|
||||
"uuid": "^14.0.0"
|
||||
"uuid": "^14.0.0",
|
||||
"cli-table3": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"string-width": "^4.2.3"
|
||||
}
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import type { MouseEvent, ReactNode } from "react";
|
||||
import { useState } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
|
@ -29,19 +29,6 @@ interface ProviderStats {
|
|||
codexFastActive?: boolean;
|
||||
}
|
||||
|
||||
const KIND_LABEL: Record<string, string> = {
|
||||
llm: "Chat",
|
||||
embedding: "Embed",
|
||||
image: "Image",
|
||||
imageToText: "I→T",
|
||||
tts: "TTS",
|
||||
stt: "STT",
|
||||
webSearch: "Search",
|
||||
webFetch: "Fetch",
|
||||
video: "Video",
|
||||
music: "Music",
|
||||
};
|
||||
|
||||
interface ProviderCardProps {
|
||||
providerId: string;
|
||||
provider: {
|
||||
|
|
@ -62,20 +49,6 @@ interface ProviderCardProps {
|
|||
onToggle: (active: boolean) => void;
|
||||
}
|
||||
|
||||
const DOT_COLORS: Record<string, string> = {
|
||||
free: "bg-green-500",
|
||||
"no-auth": "bg-stone-500",
|
||||
oauth: "bg-blue-500",
|
||||
apikey: "bg-amber-500",
|
||||
compatible: "bg-orange-500",
|
||||
"web-cookie": "bg-purple-500",
|
||||
search: "bg-teal-500",
|
||||
audio: "bg-rose-500",
|
||||
local: "bg-emerald-500",
|
||||
"upstream-proxy": "bg-indigo-500",
|
||||
"cloud-agent": "bg-violet-500",
|
||||
};
|
||||
|
||||
function getStatusDisplay(
|
||||
connected: number,
|
||||
error: number,
|
||||
|
|
@ -128,6 +101,39 @@ export default function ProviderCard({
|
|||
const tp = useTranslations("miniPlayground");
|
||||
const [testExpanded, setTestExpanded] = useState<boolean>(false);
|
||||
|
||||
const KIND_LABEL = useMemo<Record<string, string>>(
|
||||
() => ({
|
||||
llm: "Chat",
|
||||
embedding: "Embed",
|
||||
image: "Image",
|
||||
imageToText: "I→T",
|
||||
tts: "TTS",
|
||||
stt: "STT",
|
||||
webSearch: "Search",
|
||||
webFetch: "Fetch",
|
||||
video: "Video",
|
||||
music: "Music",
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const DOT_COLORS = useMemo<Record<string, string>>(
|
||||
() => ({
|
||||
free: "bg-green-500",
|
||||
"no-auth": "bg-stone-500",
|
||||
oauth: "bg-blue-500",
|
||||
apikey: "bg-amber-500",
|
||||
compatible: "bg-orange-500",
|
||||
"web-cookie": "bg-purple-500",
|
||||
search: "bg-teal-500",
|
||||
audio: "bg-rose-500",
|
||||
local: "bg-emerald-500",
|
||||
"upstream-proxy": "bg-indigo-500",
|
||||
"cloud-agent": "bg-violet-500",
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
// Show the Test button for LLM providers (when serviceKinds includes "llm"
|
||||
// OR when the provider has no explicit serviceKinds but is a regular LLM provider
|
||||
// i.e. not a search/audio/cloud-agent type).
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
|
||||
const KIRO_AUTH_SERVICE = "https://prod.us-east-1.auth.desktop.kiro.dev";
|
||||
import { KIRO_CONFIG } from "@/lib/oauth/constants/oauth";
|
||||
|
||||
/**
|
||||
* GET /api/oauth/kiro/social-authorize
|
||||
|
|
@ -26,11 +25,11 @@ export async function GET(request) {
|
|||
|
||||
const loginProvider = provider === "google" ? "Google" : "Github";
|
||||
|
||||
const response = await fetch(`${KIRO_AUTH_SERVICE}/oauth/device/authorization`, {
|
||||
const response = await fetch(KIRO_CONFIG.socialDeviceAuthorizeUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
clientId: "kiro-cli",
|
||||
clientId: KIRO_CONFIG.socialClientId,
|
||||
loginProvider,
|
||||
}),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,8 +6,7 @@ import { getConsistentMachineId } from "@/shared/utils/machineId";
|
|||
import { syncToCloud } from "@/lib/cloudSync";
|
||||
import { isAuthRequired, isAuthenticated } from "@/shared/utils/apiAuth";
|
||||
import { validateBody, isValidationFailure } from "@/shared/validation/helpers";
|
||||
|
||||
const KIRO_AUTH_SERVICE = "https://prod.us-east-1.auth.desktop.kiro.dev";
|
||||
import { KIRO_CONFIG } from "@/lib/oauth/constants/oauth";
|
||||
|
||||
const socialExchangeSchema = z.object({
|
||||
deviceCode: z.string().min(1, "Missing deviceCode or provider"),
|
||||
|
|
@ -50,10 +49,10 @@ export async function POST(request: Request) {
|
|||
try {
|
||||
const { deviceCode, provider } = validation.data;
|
||||
|
||||
const response = await fetch(`${KIRO_AUTH_SERVICE}/oauth/device/poll`, {
|
||||
const response = await fetch(KIRO_CONFIG.socialDevicePollUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ deviceCode, clientId: "kiro-cli" }),
|
||||
body: JSON.stringify({ deviceCode, clientId: KIRO_CONFIG.socialClientId }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
|
|
|||
|
|
@ -258,6 +258,15 @@ export const KIRO_CONFIG = {
|
|||
socialLoginUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/login",
|
||||
socialTokenUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/oauth/token",
|
||||
socialRefreshUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/refreshToken",
|
||||
// Social device-code flow (Google/GitHub).
|
||||
// `socialClientId` is a public CLI identifier — Kiro's device endpoint accepts
|
||||
// any non-empty string and behaves like a User-Agent rather than a secret.
|
||||
// The env override exists so operators on locked-down builds can pin a
|
||||
// custom value if AWS ever starts enforcing this field (Hard Rule #11 spirit).
|
||||
socialClientId: process.env.KIRO_OAUTH_CLIENT_ID || "kiro-cli",
|
||||
socialDeviceAuthorizeUrl:
|
||||
"https://prod.us-east-1.auth.desktop.kiro.dev/oauth/device/authorization",
|
||||
socialDevicePollUrl: "https://prod.us-east-1.auth.desktop.kiro.dev/oauth/device/poll",
|
||||
// Auth methods
|
||||
authMethods: ["builder-id", "idc", "google", "github", "import"],
|
||||
};
|
||||
|
|
|
|||
81
tests/unit/authz/proxy-contract.test.ts
Normal file
81
tests/unit/authz/proxy-contract.test.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* Next.js 16 Proxy File Contract — Lockdown Test
|
||||
*
|
||||
* Next.js 16 deprecated `middleware.ts` in favour of `proxy.ts` (commit
|
||||
* 3fb72b973 renamed our copy to match the new convention). The framework
|
||||
* only invokes this file when ALL of the following hold:
|
||||
*
|
||||
* 1. The file lives at `src/proxy.ts` (since `src/app` is the app dir).
|
||||
* 2. It exports a function named exactly `proxy` (or default).
|
||||
* 3. The function delegates to `runAuthzPipeline` with `enforce: true`.
|
||||
* 4. The `config.matcher` covers every prefix routes are mounted under,
|
||||
* so unauthenticated requests cannot slip past the centralized
|
||||
* authorization tiers (PUBLIC / CLIENT_API / MANAGEMENT).
|
||||
*
|
||||
* Without ANY of these guarantees the pipeline silently becomes dead code
|
||||
* and every `/api/*` route falls back to per-route self-enforcement, which
|
||||
* is the failure mode that led to the v3.8.4 hardening pass. Lock the
|
||||
* contract down here so a future rename / refactor cannot regress it
|
||||
* unnoticed.
|
||||
*
|
||||
* @see docs/security/ROUTE_GUARD_TIERS.md
|
||||
* @see https://nextjs.org/docs/app/api-reference/file-conventions/proxy
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
|
||||
test("Next.js 16 proxy file exists at src/proxy.ts (not src/middleware.ts)", () => {
|
||||
assert.ok(fs.existsSync("src/proxy.ts"), "src/proxy.ts must exist (Next.js 16 file convention)");
|
||||
assert.ok(
|
||||
!fs.existsSync("src/middleware.ts"),
|
||||
"src/middleware.ts must NOT exist — Next.js 16 deprecated middleware.ts, the active file is src/proxy.ts"
|
||||
);
|
||||
});
|
||||
|
||||
test("proxy.ts exports a function named 'proxy' (Next.js 16 requires this exact name)", () => {
|
||||
const content = fs.readFileSync("src/proxy.ts", "utf8");
|
||||
assert.match(
|
||||
content,
|
||||
/export\s+async\s+function\s+proxy\s*\(/,
|
||||
"must export `async function proxy(...)` — Next.js 16 only invokes this exact name"
|
||||
);
|
||||
});
|
||||
|
||||
test("proxy.ts delegates to runAuthzPipeline with enforce: true", () => {
|
||||
const content = fs.readFileSync("src/proxy.ts", "utf8");
|
||||
assert.match(
|
||||
content,
|
||||
/runAuthzPipeline\([^)]*\{\s*enforce:\s*true\s*\}\s*\)/,
|
||||
"must call runAuthzPipeline with { enforce: true } — otherwise the pipeline runs in observe-only mode and never blocks"
|
||||
);
|
||||
});
|
||||
|
||||
test("proxy.ts config.matcher covers every /api/* route plus dashboard and v1 aliases", () => {
|
||||
const content = fs.readFileSync("src/proxy.ts", "utf8");
|
||||
// Required prefixes — drop one and the corresponding routes go unguarded.
|
||||
const requiredMatchers = [
|
||||
'"/api/:path*"',
|
||||
'"/dashboard/:path*"',
|
||||
'"/v1/:path*"',
|
||||
'"/chat/:path*"',
|
||||
'"/responses/:path*"',
|
||||
'"/codex/:path*"',
|
||||
'"/models"',
|
||||
];
|
||||
for (const matcher of requiredMatchers) {
|
||||
assert.ok(
|
||||
content.includes(matcher),
|
||||
`proxy.ts config.matcher must include ${matcher} — otherwise routes under that prefix bypass the authz pipeline`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("proxy.ts does not declare runtime: 'edge' (Next.js 16 proxy is Node-only)", () => {
|
||||
const content = fs.readFileSync("src/proxy.ts", "utf8");
|
||||
assert.ok(
|
||||
!/runtime:\s*['"]edge['"]/.test(content),
|
||||
"proxy.ts MUST NOT set runtime: 'edge' — Next.js 16 only supports nodejs in proxy.ts. The pipeline depends on Node-only modules (jose, better-sqlite3) and would crash at request time on the edge runtime."
|
||||
);
|
||||
});
|
||||
76
tests/unit/oauth-kiro.test.ts
Normal file
76
tests/unit/oauth-kiro.test.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Kiro IDE OAuth Configuration — Lockdown Tests
|
||||
*
|
||||
* Ensures the Kiro device-code social-login flow reads its CLI identifier and
|
||||
* AWS auth-service URLs from `KIRO_CONFIG` instead of inline literals. The
|
||||
* strings (`"kiro-cli"` and `https://prod.us-east-1.auth.desktop.kiro.dev/...`)
|
||||
* used to live duplicated across `social-authorize/route.ts` and
|
||||
* `social-exchange/route.ts`. Centralising them avoids drift if AWS ever
|
||||
* publishes a per-customer client id and matches the
|
||||
* `CLAUDE_CONFIG`/`CODEX_CONFIG` pattern that allows operators to override
|
||||
* via `process.env.KIRO_OAUTH_CLIENT_ID` (Hard Rule #11 spirit — operators
|
||||
* can pin a custom id without patching source).
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import { KIRO_CONFIG } from "../../src/lib/oauth/constants/oauth";
|
||||
|
||||
test("KIRO_CONFIG exposes the social-flow client id and device-code URLs", () => {
|
||||
assert.equal(typeof KIRO_CONFIG.socialClientId, "string");
|
||||
assert.ok(KIRO_CONFIG.socialClientId.length > 0, "socialClientId must be non-empty");
|
||||
assert.ok(
|
||||
KIRO_CONFIG.socialDeviceAuthorizeUrl.startsWith("https://"),
|
||||
"socialDeviceAuthorizeUrl must use HTTPS"
|
||||
);
|
||||
assert.ok(
|
||||
KIRO_CONFIG.socialDevicePollUrl.startsWith("https://"),
|
||||
"socialDevicePollUrl must use HTTPS"
|
||||
);
|
||||
assert.ok(
|
||||
KIRO_CONFIG.socialDeviceAuthorizeUrl.endsWith("/oauth/device/authorization"),
|
||||
"socialDeviceAuthorizeUrl must point at the AWS Kiro device authorize endpoint"
|
||||
);
|
||||
assert.ok(
|
||||
KIRO_CONFIG.socialDevicePollUrl.endsWith("/oauth/device/poll"),
|
||||
"socialDevicePollUrl must point at the AWS Kiro device poll endpoint"
|
||||
);
|
||||
});
|
||||
|
||||
test("KIRO_CONFIG.socialClientId default matches the public CLI identifier 'kiro-cli'", () => {
|
||||
// We only assert the default when no env override is set, so CI can pin a
|
||||
// custom id via KIRO_OAUTH_CLIENT_ID without breaking this test.
|
||||
if (!process.env.KIRO_OAUTH_CLIENT_ID) {
|
||||
assert.equal(KIRO_CONFIG.socialClientId, "kiro-cli");
|
||||
}
|
||||
});
|
||||
|
||||
test("Kiro social-flow routes do not duplicate the AWS auth URL or 'kiro-cli' literal", () => {
|
||||
// Catches future refactors that copy a hard-coded URL/identifier back into
|
||||
// either route. The string "kiro-cli" may still appear as an env-var name
|
||||
// in comments, so we grep for the specific *literal* shapes we replaced.
|
||||
const routePaths = [
|
||||
"src/app/api/oauth/kiro/social-authorize/route.ts",
|
||||
"src/app/api/oauth/kiro/social-exchange/route.ts",
|
||||
];
|
||||
for (const routePath of routePaths) {
|
||||
const content = fs.readFileSync(routePath, "utf8");
|
||||
assert.ok(
|
||||
content.includes('from "@/lib/oauth/constants/oauth"'),
|
||||
`${routePath} must import KIRO_CONFIG from @/lib/oauth/constants/oauth`
|
||||
);
|
||||
assert.ok(
|
||||
content.includes("KIRO_CONFIG.socialClientId"),
|
||||
`${routePath} must reference KIRO_CONFIG.socialClientId instead of the "kiro-cli" literal`
|
||||
);
|
||||
assert.ok(
|
||||
!/['"]kiro-cli['"]/.test(content),
|
||||
`${routePath} must NOT inline the "kiro-cli" literal — use KIRO_CONFIG.socialClientId`
|
||||
);
|
||||
assert.ok(
|
||||
!/['"]https:\/\/prod\.us-east-1\.auth\.desktop\.kiro\.dev/.test(content),
|
||||
`${routePath} must NOT inline the Kiro auth service URL — use KIRO_CONFIG.socialDeviceAuthorizeUrl / socialDevicePollUrl`
|
||||
);
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue