WIP snapshot asus-20261005 (Documents/0pt) #358
-204
@@ -1,204 +0,0 @@
|
||||
# =============================================================================
|
||||
# 0pt (edge plane) -- environment inventory / example environment
|
||||
# =============================================================================
|
||||
# Copy to `.env` (gitignored) and fill in real values. Never commit real values.
|
||||
# Vercel env for the edge app; VPS .env (git-ignored) for infra/docker-compose.yml.
|
||||
# See docs/08-infrastructure.md for the canonical description of each integration.
|
||||
#
|
||||
# Two provenance markers are used below and they mean different things:
|
||||
# * "wired in code today" -- read via a `process.env.*` somewhere in
|
||||
# this repo right now (grep-verified).
|
||||
# * "documented target, not yet read" -- named in docs/08-infrastructure.md /
|
||||
# docs/02-architecture.md but with no
|
||||
# `process.env.*` read yet. Filling it in
|
||||
# unblocks the integration; it does not by
|
||||
# itself change behaviour.
|
||||
#
|
||||
# The app is designed to build and boot with all of these UNSET: lib/db.ts and
|
||||
# lib/stripe.ts are lazy + env-guarded, and the storage/payment/doc routes return
|
||||
# HTTP 501 ("not configured") until the relevant keys are provided.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Core / public
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public base URL of this app (used for absolute links / callbacks).
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
# Documented target (docs/08); no call site found reading it in this pass.
|
||||
# NOTE: api.0pt.app is a dead hostname (falls through to wildcard Vercel ->
|
||||
# DEPLOYMENT_NOT_FOUND). The app serves its own /api routes on the site
|
||||
# origin, so the honest base URL is the live origin itself.
|
||||
NEXT_PUBLIC_API_URL=https://0pt.app
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Database (Postgres) -- wired in code today: lib/db.ts, app/api/health
|
||||
# ---------------------------------------------------------------------------
|
||||
# Vercel Postgres sets POSTGRES_URL / POSTGRES_PRISMA_URL automatically when the
|
||||
# integration is attached. First one set wins, in this order (lib/db.ts):
|
||||
# DATABASE_URL -> POSTGRES_URL -> POSTGRES_PRISMA_URL.
|
||||
DATABASE_URL=postgres://user:password@localhost:5432/opt
|
||||
POSTGRES_URL=
|
||||
POSTGRES_PRISMA_URL=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cache / queue (Redis) -- app/api/health only checks presence; no other call
|
||||
# site found reading it in this pass (worker queue usage, if any, lives in
|
||||
# services/api, outside this repo's scope).
|
||||
# ---------------------------------------------------------------------------
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Auth -- session / dev-login / service-token secrets (wired in code today)
|
||||
# ---------------------------------------------------------------------------
|
||||
# Session-signing secret (generate a long random string).
|
||||
AUTH_SECRET=change-me-long-random-string
|
||||
# AUTH_URL is documented (docs/08) but not yet read by any route in this pass.
|
||||
AUTH_URL=https://0pt.app
|
||||
# Optional dev-only login shortcut key (leave UNSET in production).
|
||||
DEV_LOGIN_KEY=
|
||||
# Shared token for internal/service-to-service calls; sent as X-Internal-Token
|
||||
# on every engineFetch() call (lib/engine.ts).
|
||||
INTERNAL_TOKEN=change-me-internal-token
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Zitadel / OIDC Bearer verification (Wave 2 hardening) -- wired in code today
|
||||
# ---------------------------------------------------------------------------
|
||||
# 0pt verifies inbound Bearer tokens against the fleet Zitadel IdP
|
||||
# (src/auth/zitadel.ts, wired via middleware.ts). JWKS are discovered from
|
||||
# <issuer>/.well-known/openid-configuration, falling back to <issuer>/oauth/v2/keys.
|
||||
#
|
||||
# Fleet Zitadel issuer (OIDC). Default when unset: https://id-customers.siax.io
|
||||
ZITADEL_ISSUER=https://id-customers.siax.io
|
||||
# Enforcement is FAIL-CLOSED. Leave this UNSET (or set it to "true") and a
|
||||
# valid, verified Bearer token is REQUIRED on /api/0pt/* -- 401 on
|
||||
# missing/invalid, 403 when X-Tenant-Id disagrees with the token tenant.
|
||||
# ONLY the literal value "false" disables authentication, and that value is
|
||||
# REFUSED when NODE_ENV=production: the app throws at startup instead of
|
||||
# serving openly. Use "false" for local development only.
|
||||
AUTH_ENFORCE=true
|
||||
# Optional override for the claim key that carries the SIAX tenant id. When
|
||||
# unset, tenant derivation uses the documented precedence:
|
||||
# urn:zitadel:iam:org:id -> urn:zitadel:iam:user:resourceowner:id -> tenant_id -> tenant.
|
||||
ZITADEL_TENANT_CLAIM=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Data engine bridge -- wired in code today: lib/engine.ts
|
||||
# ---------------------------------------------------------------------------
|
||||
# When set, edge route handlers defer to the FastAPI engine (services/api) as the
|
||||
# single source of truth (orders/estimates/catalog/ai-parse). When unset, the edge
|
||||
# computes locally / falls back to an honest heuristic -- see lib/engine.ts and
|
||||
# app/api/ai/parse.
|
||||
ENGINE_URL=http://localhost:8000
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SIAX platform clients
|
||||
# ---------------------------------------------------------------------------
|
||||
# WIRED FOR REAL since 2026-08-31: every factory in src/clients/* reads its
|
||||
# *_URL from process.env when no constructor baseUrl is given. Under
|
||||
# NODE_ENV=production an UNSET url is a hard boot failure
|
||||
# (SiaxError DEPENDENCY_UNAVAILABLE) — production never silently falls back
|
||||
# to the in-memory mocks (Master-DoD §3/§10). In dev/test, unset = documented
|
||||
# local mock/no-op behaviour.
|
||||
CL0UD_URL=
|
||||
N0D_URL=
|
||||
AUD0_URL=
|
||||
# Optional service bearer tokens for the platform clients (machine identity).
|
||||
# Values come from Infisical/secret refs — NEVER commit real values here.
|
||||
CL0UD_SERVICE_TOKEN=
|
||||
N0D_SERVICE_TOKEN=
|
||||
AUD0_SERVICE_TOKEN=
|
||||
ST0RE_SERVICE_TOKEN=
|
||||
# INF0 (AI OS) -- documented target, not yet read by any code in this repo.
|
||||
# Route AI-parsning / model calls through the gateway instead of raw provider keys.
|
||||
INF0_URL=
|
||||
# ST0RE_URL is the convention name for src/clients/st0re-client.ts /
|
||||
# st0re-lakehouse-client.ts and is read by the real-infra test suite
|
||||
# (tests/p0/*.realinfra.test.ts). The lakehouse write path was live-verified
|
||||
# against ST0RE on 2026-07-13 with this set; unset, the clients fall back to
|
||||
# their deterministic in-memory mocks.
|
||||
ST0RE_URL=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# P0 Data-OS backends (src/search, src/analytics, src/metadata, src/features)
|
||||
# ---------------------------------------------------------------------------
|
||||
# OpenSearch — search projections ONLY, never canonical truth (§42). Unset in
|
||||
# dev/test = search capability unavailable (typed error), never a fake.
|
||||
OPENSEARCH_URL=
|
||||
# ClickHouse — 0pt capability analytics (tenant-isolated metric events).
|
||||
# HAS NO CONNECTION to b0ts pipelines or quarantined market data (legal).
|
||||
CLICKHOUSE_URL=
|
||||
# "user:password" for ClickHouse basic auth; from secret refs only.
|
||||
CLICKHOUSE_AUTH=
|
||||
# OpenMetadata — metadata PROJECTION target (canonical stays in 0pt).
|
||||
OPENMETADATA_URL=
|
||||
OPENMETADATA_TOKEN=
|
||||
# Feast feature server (online features path).
|
||||
FEAST_SERVER_URL=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Payments (Stripe) -- wired in code today: app/api/payments/*,
|
||||
# app/api/webhooks/stripe
|
||||
# ---------------------------------------------------------------------------
|
||||
STRIPE_SECRET_KEY=sk_test_xxx
|
||||
STRIPE_WEBHOOK_SECRET=whsec_xxx
|
||||
# Documented target (docs/08); not yet read by any route in this pass.
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Object storage -- Cloudflare R2 / S3-compatible
|
||||
# ---------------------------------------------------------------------------
|
||||
# R2_ENDPOINT / R2_BUCKET / R2_ACCESS_KEY_ID are read in code today; all three
|
||||
# are required to enable /api/uploads, otherwise it returns 501.
|
||||
# R2_ACCOUNT_ID and R2_SECRET_ACCESS_KEY are documented in docs/08 but were NOT
|
||||
# found via a process.env.* read in this pass -- verify before assuming the R2
|
||||
# client is fully wired.
|
||||
R2_ENDPOINT=https://<accountid>.r2.cloudflarestorage.com
|
||||
R2_BUCKET=opt-uploads
|
||||
R2_ACCESS_KEY_ID=change-me
|
||||
R2_ACCOUNT_ID=
|
||||
R2_SECRET_ACCESS_KEY=
|
||||
# Fallback local artifact store used when no object storage is configured
|
||||
# (src/adapters/local-blob-store.ts). Leave unset in production.
|
||||
OPT_LOCAL_BLOB_DIR=./.opt-blobs
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Document intelligence / OCR (Azure) -- wired in code today:
|
||||
# src/services/document-extraction-service.ts. Unset => extraction
|
||||
# "not configured".
|
||||
# ---------------------------------------------------------------------------
|
||||
DOC_INTELLIGENCE_ENDPOINT=https://<resource>.cognitiveservices.azure.com
|
||||
DOC_INTELLIGENCE_KEY=change-me
|
||||
DOC_INTELLIGENCE_API_VERSION=2024-11-30
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Customer DB-grant encryption -- wired in code today:
|
||||
# /api/connections/db-grant
|
||||
# ---------------------------------------------------------------------------
|
||||
# 32-byte key, AES-GCM. Encrypts customer-supplied read-only connection strings
|
||||
# at rest.
|
||||
GRANT_ENC_KEY=change-me-32-byte-key
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# External write-back targets
|
||||
# ---------------------------------------------------------------------------
|
||||
# Odoo base URL for the bills/write-back adapter.
|
||||
ODOO_BASE_URL=https://<your-odoo-host>
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Email -- Resend (documented target, docs/08; no Resend call site found in
|
||||
# this pass -- treat as not-yet-implemented, not just unconfigured)
|
||||
# ---------------------------------------------------------------------------
|
||||
RESEND_API_KEY=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Embedded analytics (documented target, docs/08; no call site found in this
|
||||
# pass)
|
||||
# ---------------------------------------------------------------------------
|
||||
METABASE_SITE_URL=
|
||||
METABASE_SECRET_KEY=
|
||||
NOCODB_BASE_URL=
|
||||
NOCODB_API_TOKEN=
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Test-only (tests/p0 real-infra suite; not needed to run the app)
|
||||
# ---------------------------------------------------------------------------
|
||||
# ST0RE_TEST_TENANT_ID=
|
||||
Submodule
+1
Submodule .worktrees/feat-import-policy added at c134527509
@@ -1,70 +0,0 @@
|
||||
"""The engine must not accept anonymous traffic when INTERNAL_TOKEN is set.
|
||||
|
||||
Background (2026-09-15): lib/engine.ts on the edge has always SENT
|
||||
`X-Internal-Token`, but the engine never verified it — a deployed engine would
|
||||
have accepted anyone who could reach the port. The gate is fail-closed:
|
||||
|
||||
* INTERNAL_TOKEN set -> every route except /health requires the EXACT
|
||||
token; wrong/missing = 401.
|
||||
* INTERNAL_TOKEN unset -> gate inert (local dev); production compose always
|
||||
sets it.
|
||||
|
||||
`/health` stays public on purpose: it is the liveness probe that
|
||||
`lib/health-checks.ts` calls from the edge, and it leaks nothing but booleans.
|
||||
|
||||
The token is read at module import (the same value the running process serves
|
||||
with), so each test reloads the module with the env it wants — and the
|
||||
teardown reloads it one more time with the env CLEARED so later test files in
|
||||
the same session never inherit a baked-in token.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def engine(monkeypatch):
|
||||
monkeypatch.setenv("INTERNAL_TOKEN", "s3cret")
|
||||
import orchestrator.main as main
|
||||
|
||||
importlib.reload(main)
|
||||
yield TestClient(main.app)
|
||||
os.environ.pop("INTERNAL_TOKEN", None)
|
||||
importlib.reload(main)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def engine_no_token(monkeypatch):
|
||||
monkeypatch.delenv("INTERNAL_TOKEN", raising=False)
|
||||
import orchestrator.main as main
|
||||
|
||||
importlib.reload(main)
|
||||
yield TestClient(main.app)
|
||||
os.environ.pop("INTERNAL_TOKEN", None)
|
||||
importlib.reload(main)
|
||||
|
||||
|
||||
def test_gate_off_when_internal_token_unset(engine_no_token):
|
||||
assert engine_no_token.get("/health").status_code == 200
|
||||
assert engine_no_token.get("/sources").status_code == 200
|
||||
|
||||
|
||||
def test_health_stays_public_but_sources_require_token(engine):
|
||||
# liveness probe is public by design
|
||||
assert engine.get("/health").status_code == 200
|
||||
# anonymous call to a data route -> 401 (was 200 before 2026-09-15)
|
||||
r = engine.get("/sources")
|
||||
assert r.status_code == 401
|
||||
assert r.json()["error"]["code"] == "unauthorized"
|
||||
|
||||
|
||||
def test_wrong_token_is_401_and_correct_token_passes(engine):
|
||||
wrong = engine.get("/estimates?markets=SE", headers={"X-Internal-Token": "nope"})
|
||||
assert wrong.status_code == 401
|
||||
right = engine.get("/sources", headers={"X-Internal-Token": "s3cret"})
|
||||
assert right.status_code == 200
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* `src/auth/internal-token.ts` -- verification for the ONE machine-to-machine
|
||||
* credential 0pt already uses for engine (Python) -> edge (Next.js) calls:
|
||||
* the `X-Internal-Token` header, checked against the `INTERNAL_TOKEN` env var
|
||||
* (the same shared secret `lib/engine.ts` and `lib/health-checks.ts` already
|
||||
* send on the opposite-direction edge -> engine calls).
|
||||
*
|
||||
* P014 FIX: `services/api/workers/quality_bridge.py` has always called
|
||||
* `POST /api/0pt/quality/bridge-run` with `X-Internal-Token`, never an
|
||||
* `Authorization: Bearer` header. But this route sits under
|
||||
* `/api/0pt/:path*`, which `middleware.ts` guards unconditionally with real
|
||||
* Zitadel Bearer verification -- and nothing anywhere checked
|
||||
* `X-Internal-Token` as an alternative credential. The result: in any
|
||||
* environment with the default `AUTH_ENFORCE` posture (enforced), the
|
||||
* Python engine's ONLY call into this bridge was rejected with 401 by
|
||||
* middleware before the route handler ever ran. `OPT_QUALITY_BRIDGE_ENABLED=true`
|
||||
* therefore could never actually succeed against a real deployment --
|
||||
* quality_bridge.py's fail-open design (see its own docstring) silently
|
||||
* absorbed the 401 as `status: "unavailable"`, so nothing outwardly broke,
|
||||
* but the whole point of building the bridge (an advisory second opinion
|
||||
* from the TS QualityService) was unreachable. Neither test suite caught
|
||||
* this: the TS test (tests/p0/quality-bridge.test.ts) calls the route's
|
||||
* POST function directly, bypassing middleware.ts entirely; the Python test
|
||||
* (services/api/tests/test_quality_bridge.py) mocks the HTTP layer and never
|
||||
* exercises the edge's real auth gate.
|
||||
*
|
||||
* This module verifies `X-Internal-Token` in constant time. middleware.ts
|
||||
* accepts it ONLY for the one route the engine actually calls
|
||||
* (`/api/0pt/quality/bridge-run`) -- not a blanket bypass for the rest of
|
||||
* `/api/0pt/*`. Fails closed: an unset/empty `INTERNAL_TOKEN` env var never
|
||||
* authenticates, even against an empty or missing header (no accidental
|
||||
* "unset equals unset, so let it through").
|
||||
*
|
||||
* Uses only Web APIs (`TextEncoder`), not `node:crypto` -- Next.js
|
||||
* middleware runs on the Edge runtime, which does not expose Node's
|
||||
* `crypto.timingSafeEqual`.
|
||||
*/
|
||||
|
||||
/** Minimal header accessor -- Web `Headers` and `NextRequest.headers` satisfy this. */
|
||||
export interface HeaderLookup {
|
||||
get(name: string): string | null | undefined;
|
||||
}
|
||||
|
||||
export const INTERNAL_TOKEN_HEADER = "x-internal-token";
|
||||
|
||||
/** Constant-time-ish byte comparison (Edge-runtime safe: no node:crypto). */
|
||||
function timingSafeEqualStr(a: string, b: string): boolean {
|
||||
const enc = new TextEncoder();
|
||||
const aBytes = enc.encode(a);
|
||||
const bBytes = enc.encode(b);
|
||||
const len = Math.max(aBytes.length, bBytes.length);
|
||||
let diff = aBytes.length ^ bBytes.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
diff |= (aBytes[i] ?? 0) ^ (bBytes[i] ?? 0);
|
||||
}
|
||||
return diff === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* True only when `X-Internal-Token` is present AND matches a non-empty
|
||||
* configured `INTERNAL_TOKEN`. Fails closed on every other combination
|
||||
* (missing header, missing/empty env var, mismatch).
|
||||
*/
|
||||
export function verifyInternalServiceToken(
|
||||
headers: HeaderLookup,
|
||||
env: { INTERNAL_TOKEN?: string } = process.env,
|
||||
): boolean {
|
||||
const expected = env.INTERNAL_TOKEN?.trim();
|
||||
if (!expected) return false;
|
||||
const provided = headers.get(INTERNAL_TOKEN_HEADER);
|
||||
if (!provided) return false;
|
||||
return timingSafeEqualStr(provided, expected);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { verifyInternalServiceToken, INTERNAL_TOKEN_HEADER } from "@/src/auth/internal-token";
|
||||
|
||||
function headers(map: Record<string, string> = {}): { get(name: string): string | null } {
|
||||
const lower = new Map(Object.entries(map).map(([k, v]) => [k.toLowerCase(), v]));
|
||||
return { get: (name: string) => lower.get(name.toLowerCase()) ?? null };
|
||||
}
|
||||
|
||||
describe("verifyInternalServiceToken", () => {
|
||||
it("accepts a header that matches the configured INTERNAL_TOKEN exactly", () => {
|
||||
const ok = verifyInternalServiceToken(headers({ [INTERNAL_TOKEN_HEADER]: "s3cr3t" }), {
|
||||
INTERNAL_TOKEN: "s3cr3t",
|
||||
});
|
||||
expect(ok).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects a mismatched token", () => {
|
||||
const ok = verifyInternalServiceToken(headers({ [INTERNAL_TOKEN_HEADER]: "wrong" }), {
|
||||
INTERNAL_TOKEN: "s3cr3t",
|
||||
});
|
||||
expect(ok).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects a missing header even when INTERNAL_TOKEN is configured", () => {
|
||||
const ok = verifyInternalServiceToken(headers({}), { INTERNAL_TOKEN: "s3cr3t" });
|
||||
expect(ok).toBe(false);
|
||||
});
|
||||
|
||||
it("FAILS CLOSED: an unset INTERNAL_TOKEN never authenticates, even against an empty header value", () => {
|
||||
// Regression guard: quality_bridge.py sends `os.getenv("INTERNAL_TOKEN", "")`
|
||||
// -- if the edge's own INTERNAL_TOKEN were also unset, a naive `provided
|
||||
// === expected` comparison of two empty strings would wrongly authenticate.
|
||||
const ok = verifyInternalServiceToken(headers({ [INTERNAL_TOKEN_HEADER]: "" }), { INTERNAL_TOKEN: undefined });
|
||||
expect(ok).toBe(false);
|
||||
});
|
||||
|
||||
it("FAILS CLOSED: an unset INTERNAL_TOKEN never authenticates even a non-empty header", () => {
|
||||
const ok = verifyInternalServiceToken(headers({ [INTERNAL_TOKEN_HEADER]: "anything" }), {
|
||||
INTERNAL_TOKEN: undefined,
|
||||
});
|
||||
expect(ok).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects a whitespace-only configured token (trimmed to empty)", () => {
|
||||
const ok = verifyInternalServiceToken(headers({ [INTERNAL_TOKEN_HEADER]: " " }), { INTERNAL_TOKEN: " " });
|
||||
expect(ok).toBe(false);
|
||||
});
|
||||
|
||||
it("is case-sensitive on the header name lookup contract but tolerant of header-name casing via the HeaderLookup adapter", () => {
|
||||
const ok = verifyInternalServiceToken(headers({ "X-Internal-Token": "s3cr3t" }), { INTERNAL_TOKEN: "s3cr3t" });
|
||||
expect(ok).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
/**
|
||||
* P014 regression test: before this pass, POST /api/0pt/quality/bridge-run
|
||||
* -- the ONE route the Python engine's quality_bridge.py actually calls,
|
||||
* using X-Internal-Token, never a Bearer token -- was rejected 401 by
|
||||
* middleware.ts in any environment enforcing auth (the default), because
|
||||
* middleware required a verified Zitadel Bearer token unconditionally on
|
||||
* every /api/0pt/* path. See src/auth/internal-token.ts's doc comment for
|
||||
* the full story. Neither existing test suite caught it: the TS bridge test
|
||||
* calls the route's POST function directly (bypassing middleware.ts
|
||||
* entirely) and the Python test mocks the HTTP layer.
|
||||
*
|
||||
* This test exercises the actual `middleware` function, not the route
|
||||
* handler, so it proves the fix at the layer where the bug lived.
|
||||
*/
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||
import { NextRequest } from 'next/server';
|
||||
import { middleware } from '../../middleware';
|
||||
|
||||
function req(path: string, headers: Record<string, string> = {}): NextRequest {
|
||||
return new NextRequest(new URL(path, 'http://localhost'), { headers });
|
||||
}
|
||||
|
||||
describe('middleware.ts: internal-token path for /api/0pt/quality/bridge-run', () => {
|
||||
const saved = { auth: process.env.AUTH_ENFORCE, internal: process.env.INTERNAL_TOKEN, node: process.env.NODE_ENV };
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.AUTH_ENFORCE; // default posture: enforced
|
||||
process.env.INTERNAL_TOKEN = 'engine-shared-secret';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const [k, v] of Object.entries(saved)) {
|
||||
const envKey = k === 'auth' ? 'AUTH_ENFORCE' : k === 'internal' ? 'INTERNAL_TOKEN' : 'NODE_ENV';
|
||||
if (v === undefined) delete process.env[envKey];
|
||||
else process.env[envKey] = v;
|
||||
}
|
||||
});
|
||||
|
||||
it('REGRESSION: a valid X-Internal-Token lets the engine through with NO Bearer token (was: 401)', async () => {
|
||||
const res = await middleware(
|
||||
req('/api/0pt/quality/bridge-run', { 'x-internal-token': 'engine-shared-secret' }),
|
||||
);
|
||||
// NextResponse.next() carries no body/status override -- it is not an
|
||||
// error JSON response the way a rejection is.
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
|
||||
it('an invalid X-Internal-Token still falls through to the ordinary Bearer requirement (401, not a bypass)', async () => {
|
||||
const res = await middleware(req('/api/0pt/quality/bridge-run', { 'x-internal-token': 'wrong-secret' }));
|
||||
expect(res.status).toBe(401);
|
||||
const body = await res.json();
|
||||
expect(body.error).toBe('OPT_UNAUTHENTICATED');
|
||||
});
|
||||
|
||||
it('a valid X-Internal-Token on a DIFFERENT /api/0pt/* route does NOT bypass auth (not a blanket exemption)', async () => {
|
||||
const res = await middleware(req('/api/0pt/sources', { 'x-internal-token': 'engine-shared-secret' }));
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('no INTERNAL_TOKEN configured on the edge -- the internal-token path never activates, even with the right-looking header', async () => {
|
||||
delete process.env.INTERNAL_TOKEN;
|
||||
const res = await middleware(
|
||||
req('/api/0pt/quality/bridge-run', { 'x-internal-token': 'engine-shared-secret' }),
|
||||
);
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
|
||||
it('a real Bearer-authenticated caller can still reach bridge-run directly (not internal-token-only)', async () => {
|
||||
// No X-Internal-Token at all, no Authorization header either -> falls to
|
||||
// the normal Bearer requirement -> 401, proving this path is additive,
|
||||
// not a replacement for the existing Zitadel flow.
|
||||
const res = await middleware(req('/api/0pt/quality/bridge-run'));
|
||||
expect(res.status).toBe(401);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user