refactor: namespace non-public endpoints under /api/internal/#455
refactor: namespace non-public endpoints under /api/internal/#455kasperpawlowski wants to merge 4 commits into
Conversation
Some third-party integrators have started consuming endpoints like /api/vaults server-side and then complaining when we ship breaking changes. We never promised stability on anything outside /api/public/* — but with no namespace separation, the contract was easy to miss. This makes the line visible and enforceable. Changes: - Move all 18 non-public endpoints into /api/internal/* via file-based routing. Includes /api/screen-address and /api/sentry-tunnel. Only /api/public/is-known and /api/public/metadata stay outside the namespace. - server/middleware/cors.ts hard-blocks any non-internal caller hitting /api/internal/* without an Origin header (curl, server-side fetch, etc.) with 403. Our own server-to-server traffic bypasses via the existing isInternalRequest() sentinel set by INTERNAL_FETCH_HEADERS. Cross-origin browser callers already 403 via the existing allow-list check; that's unchanged. - Add X-API-Stability: internal; may-break-without-notice on every non-public response so anyone reading their network tab gets the signal even if they ignore the URL prefix. - Add docs/PUBLIC_API.md listing the supported public surface and spelling out the internal namespace's lack of stability guarantees. README cross-links it. - Update every client and server caller: composables, entities, plugins, server-side utilities, body-limit middleware, warm-cache imports, sentry tunnel config, Pyth update fetch helpers in utils/pyth.ts, and tests that import handler modules by path.
|
🚅 Deployed to the euler-lite-pr-455 environment in euler-lite
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: euler-xyz/coderabbit/.coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughMigrates callers, server utilities, tests, and docs from public ChangesAPI Endpoint Internalization
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…pace-internal-api # Conflicts: # composables/useMerkl.ts
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
services/trm.ts (1)
8-15:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winCheck response status before parsing to ensure proper error fallback.
The code currently skips the error handler on non-2xx responses. If the API returns 403/500 with valid JSON (even an error object), the code parses it and returns
Boolean(data?.addressIsSuspicious)which resolves tofalse—the opposite of the safe fallback. Thecatchblock'sreturn truenever executes because no exception occurs. Add a status check to trigger the safe fallback:Suggested fix
try { const resp = await fetch('/api/internal/screen-address', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ address, vpnIsUsed }), }) + if (!resp.ok) throw new Error(`screen-address failed with status ${resp.status}`) const data = await resp.json() return Boolean(data?.addressIsSuspicious) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@services/trm.ts` around lines 8 - 15, Update the fetch handling for '/api/internal/screen-address' to treat non-2xx responses as errors before parsing: after awaiting resp from fetch, check resp.ok (or resp.status) and if it's false, trigger the safe fallback (e.g., throw or directly return true) so the catch block or fallback path runs instead of parsing error JSON; then only call resp.json() and return Boolean(data?.addressIsSuspicious) when resp.ok is true. Ensure this change is applied around the resp and data usage in services/trm.ts so the API error cases produce the safe true result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@composables/useRpcClient/index.ts`:
- Around line 16-18: The server-side RPC proxy fails in production because viem
HTTP transports created by getPublicClient(...) lack the INTERNAL_FETCH_HEADERS
sentinel, so update getPublicClient in utils/public-client.ts to accept an
optional headers?: Record<string,string> parameter and pass it into the
http(...) transport options (ensuring the cache key accounts for headers, e.g.
rpcUrl + JSON.stringify(headers) to avoid returning a client with wrong
headers); then update the composable in composables/useRpcClient/index.ts to
call getPublicClient(rpcUrl, INTERNAL_FETCH_HEADERS) when import.meta.server is
true (and call it without headers on the client).
In `@server/middleware/cors.ts`:
- Around line 118-121: The statusMessage passed to createError in the middleware
(the createError(...) call that currently uses 'Forbidden — /api/internal/* is
not a public contract. See docs/PUBLIC_API.md.') contains a non-ASCII em dash;
replace it with an ASCII-safe short statusMessage (e.g., 'Forbidden -
/api/internal/* is not a public contract') or simpler 'Forbidden', and move the
detailed text (including the docs/PUBLIC_API.md reference) into the error's
message/body field so all non-ASCII characters are removed from statusMessage
while preserving the full explanation in the response payload.
---
Outside diff comments:
In `@services/trm.ts`:
- Around line 8-15: Update the fetch handling for '/api/internal/screen-address'
to treat non-2xx responses as errors before parsing: after awaiting resp from
fetch, check resp.ok (or resp.status) and if it's false, trigger the safe
fallback (e.g., throw or directly return true) so the catch block or fallback
path runs instead of parsing error JSON; then only call resp.json() and return
Boolean(data?.addressIsSuspicious) when resp.ok is true. Ensure this change is
applied around the resp and data usage in services/trm.ts so the API error cases
produce the safe true result.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: euler-xyz/coderabbit/.coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: c8da321f-ac31-4297-941d-4e17e1b6a3a7
📒 Files selected for processing (50)
README.mdcomposables/useAccountPositions.tscomposables/useBrevis.tscomposables/useEulerAddresses.tscomposables/useEulerLabels.tscomposables/useFuul.tscomposables/useIntrinsicApy.tscomposables/useMerkl.tscomposables/useRpcClient/index.tscomposables/useTenderlySimulation.tscomposables/useTokenList.tscomposables/useTosData.tscomposables/useVaults.tsdocs/PUBLIC_API.mdentities/vault/factory.tsentities/vault/loader.tsplugins/00.wagmi.tssentry.client.config.tsserver/api/internal/euler-chains.get.tsserver/api/internal/intrinsic-apy.get.tsserver/api/internal/labels/[file].get.tsserver/api/internal/oracle-adapter.get.tsserver/api/internal/oracle-adapters.get.tsserver/api/internal/pyth/updates.get.tsserver/api/internal/rewards/brevis.get.tsserver/api/internal/rewards/fuul.get.tsserver/api/internal/rewards/merkl.get.tsserver/api/internal/rpc/[chainId].tsserver/api/internal/screen-address.post.tsserver/api/internal/sentry-tunnel.post.tsserver/api/internal/tenderly/simulate.post.tsserver/api/internal/tenderly/status.get.tsserver/api/internal/token-list.get.tsserver/api/internal/tos.get.tsserver/api/internal/vault-categories.get.tsserver/api/internal/vaults.get.tsserver/middleware/body-limit.tsserver/middleware/cors.tsserver/plugins/warm-cache.tsserver/utils/escrow-perspective.tsserver/utils/labels-helpers.tsserver/utils/labels-view.tsserver/utils/vault-categories-store.tsserver/utils/vaults-cache.tsservices/trm.tstests/server/internal-request.test.tstests/server/labels-validate-node.test.tstests/server/rewards-handlers.test.tstests/server/sentry-tunnel.test.tsutils/pyth.ts
…pace-internal-api
- Drop em dash from Forbidden statusMessage (HTTP status-line is ASCII-only; h3/Nitro sanitize non-ASCII), move the detail string into the error data payload. - Inject the loopback cf-connecting-ip sentinel on server-side viem HTTP transports so /api/internal/rpc/* calls originating inside the Nuxt process pass the new CORS internal-only check in production. - Update screen-address test import to the new /api/internal location picked up from development.
Summary
Some third-party integrators have started consuming endpoints like
/api/vaultsserver-side, then complaining when we ship breaking changes — even though we never promised stability on anything outside/api/public/*. The codebase already special-cases/api/public/inserver/middleware/cors.tsas the stable, anyone-can-call surface. Everything else was implicitly private but unlabelled, which is what made the misuse easy.This change makes the line both visible (URL prefix, response header, docs) and enforceable (hard 403 for non-browser cross-origin callers) across all 18 non-public endpoints in one cutover. The goal isn't to defeat a determined scraper — it's to give integrators an unambiguous signal they're outside the public contract and to make it impossible to consume the surface accidentally. See docs/PUBLIC_API.md for the new shape.
Highlights
server/api/internal/via Nuxt file-based routing. Includesscreen-addressandsentry-tunnel. Only/api/public/is-knownand/api/public/metadatastay outside.server/middleware/cors.ts:X-API-Stability: internal; may-break-without-noticeon every non-public response.isInternalRequest()sentinel fromserver/utils/internal-headers.ts.utils/pyth.ts, and the three tests that import handler modules by path.docs/PUBLIC_API.mdlisting the supported surface and the explicit no-stability promise for/api/internal/*. README cross-links it.Test plan
npx vue-tsc --noEmit— cleannpm run test --run— 763 passed / 1 skipped / 0 failednpm run dev— load the app, confirm every screen still renders (vault lists, market detail, position, lend, borrow, earn, rewards, ToS, Sentry tunnel). Network tab should show all internal calls hitting/api/internal/*and returning 200.DOPPLER_ENVIRONMENT=prd npm run previewor staging deploy):curl https://<deploy>/api/internal/vaults?chainId=1(no Origin) → 403.curl -H "Origin: https://example.com" .../api/internal/vaults?chainId=1→ 403.curl https://<deploy>/api/public/is-known?...→ 200.X-API-Stability: internal; may-break-without-noticeon any/api/internal/*call.Summary by CodeRabbit
Documentation
Chores