Files
OmniRoute/tests/unit/authz/proxy-contract.test.ts
diegosouzapw 448b65af2c 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.
2026-05-26 17:09:10 -03:00

82 lines
3.3 KiB
TypeScript

/**
* 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."
);
});