← all branches

feat/one-api

needs attentionviewing older commit
b19852e · incrementalpre-PRreviewed 2026-07-29 05:04 UTC0H · 1M · 5L · 9I
The branch
Purpose
Establish the dual-accept transitional auth model for the internal API mount — enabling callers to migrate from the legacy shared static secret to service tokens one at a time, with telemetry evidence to gate the eventual key deletion safely.
Goal
Complete the W2 'EXPAND' phase of the expand→migrate→contract sequence for internal API auth security upgrade (feat/one-api)
Sub-goals
  • SG-1: ServiceKey entity + api_keys table (kind discriminator)
  • SG-2: Service tokens minted from /v1/auth/token
  • SG-3: Internal mount dual-accepts service tokens
  • SG-4: Webhook secret sealed at rest
  • SG-5 gate: Record WHICH credential arrived so key deletion is evidence-based
  • W2.5: Per-request telemetry at the public mount
  • W2.5+: Same telemetry applied to internal mount (this commit)
The changes (whole branch)
What
Adds `endpointOf()` URL masking and `recordInternalAuth()` telemetry helpers to `withInternalAuth`, emitting a JSON log line identifying whether the legacy static key or a service token was presented on each accepted internal request. Tests pin three explicit invariants: resource IDs masked from endpoint, no emit on rejection, emit failure cannot fail the request. CI integration test gate extended to cover `api-telemetry-persistence.test.ts` and `public-v1-telemetry` path filter.
Why
Without knowing WHICH credential callers present, 'have all callers moved to service tokens?' was unanswerable — making the transitional dual-accept state structurally permanent. The static key could only be deleted by guessing. Same failure the public mount had before W2.5; same fix applied here.
Areas
packages/api/src/middleware+511apps/platform/src/__tests__/integration+1160.claude/rules+303.github/workflows+101
Blast
4 files, +207/−5 in this increment. Internal-auth middleware only; no public API surface changes. The telemetry is additive — no behavioral change to auth logic, only observation.
incremental-review no-pr-yet telemetry-only-change
ci· No CI runs found for this branch (no PR opened yet)coderabbit· No .coderabbit.yaml in repo

Findings · 14

correctness1

low

Length-based fallback misses exactly-32-char segments (UUID without dashes)

packages/api/src/middleware/internal-auth.ts:57

Condition is `seg.length > 32` (strictly greater). A UUID without dashes is exactly 32 chars and passes through unmasked. No current internal route uses bare 32-char IDs (all use prefixed ULIDs), so no immediate risk. Worth documenting the assumption or using `>= 26` (bare ULID length) to future-proof.

security3

low

Relative URLs → 'unknown' endpoint — may hollow out migration signal

packages/api/src/middleware/internal-auth.ts:48

In Next.js App Router route handlers, req.url is commonly a relative path (e.g. '/api/internal/bills'). new URL('/api/internal/bills') throws — caught, returns 'unknown'. If the primary call sites pass relative URLs, the telemetry exists but is silent on endpoint shape — the migration-tracking purpose is defeated. Tests only exercise absolute URLs (https://x.test/...). Worth verifying how callers pass the request object, or extracting pathname differently for relative-path inputs (e.g. treating the input as a path if URL parsing fails).

low

Masking regex requires exactly 3-char lowercase prefix

packages/api/src/middleware/internal-auth.ts:57

^[a-z]{3}_ only catches 3-char prefixes. Future IDs with 2- or 4-char prefixes (if introduced) pass through unmasked. The >32-char length fallback partially mitigates this. No current exposure — all production IDs use 3-char prefixes — but worth documenting the assumption.

info

Log injection via control characters is properly mitigated

packages/api/src/middleware/internal-auth.ts

URL constructor percent-encodes control characters in the pathname; JSON.stringify further escapes them. A crafted URL like /api/v1/foo%0abar does NOT inject a newline into the log line. No vulnerability.

conventions2

medium

Tautological instanceof branch — both arms return req.headers

packages/api/src/middleware/internal-auth.ts:104

Pre-existing: `const headers = req instanceof Request ? req.headers : req.headers` — both branches are identical. This commit widened the union type (adding `url?: string`) which was the natural moment to clean it up. Both union members expose `.headers` directly; the check is dead. Safe to simplify to `const headers = req.headers;`.

low

JSDoc on endpointOf leads with WHAT rather than WHY

packages/api/src/middleware/internal-auth.ts:42

Project convention: comments explain hidden constraints and reasons, not mechanics. The first sentence describes the masking behavior (visible from the function body). The important WHY — 'this line is retained, shipped to third parties, and read by more people than the database is' — is buried. Invert the ratio.

tests5

low

Second telemetry test inlines full AuthContext instead of reusing authOf() helper

packages/api/src/middleware/__tests__/internal-auth.test.ts

The 'names the SERVICE token' test constructs AuthContext inline. The authOf('service') helper at line 148 exists for exactly this purpose and is used everywhere else in the file. The inline literal will drift silently if AuthContext gains new required fields.

low

No test for malformed non-empty URL string

packages/api/src/middleware/__tests__/internal-auth.test.ts

Test 6 covers url=undefined → 'unknown'. The endpointOf try/catch also handles a non-empty but unparseable string (e.g. 'not-a-url') → 'unknown'. This branch is correctly implemented but has no test exercising it.

low

No telemetry assertion for the both-valid-headers short-circuit case

packages/api/src/middleware/__tests__/internal-auth.test.ts

When both x-api-key and Authorization: Bearer are valid, the legacy path short-circuits and only 'legacy_static_key' is recorded. The short-circuit itself is tested in the dual-accept suite but the telemetry labeling for that case is not pinned — a future reorder of the two checks would mislabel the credential without a failing test.

info

emitted() helper throws if non-JSON output reaches console.log

packages/api/src/middleware/__tests__/internal-auth.test.ts

emitted() calls JSON.parse on every captured line. A future debug log added to withInternalAuth would cause the helper to throw rather than return an empty array. Low risk today since recordInternalAuth is the only console.log path, but a try/catch around JSON.parse would make the helper more resilient.

info

ts field in telemetry record is never asserted in tests

packages/api/src/middleware/__tests__/internal-auth.test.ts

Tests use toMatchObject which permits extra keys — the ts field presence is never checked. Adding expect(emitted()[0]).toHaveProperty('ts') to test 1 would confirm the timestamp is present.

improvement3

low

ok handler stub defined twice in test file

packages/api/src/middleware/__tests__/internal-auth.test.ts

`const ok = async () => ({ status: 200 as const, body: 'ok' })` is defined identically in two separate describe blocks. A single module-level const would serve both.

info

endpointOf try/catch could be collapsed to remove the let declaration

packages/api/src/middleware/internal-auth.ts

Minor readability: try { const { pathname } = new URL(req.url); return pathname.split('/').map(...).join('/'); } catch { return 'unknown'; } eliminates the intermediate let and makes both exit points co-located.

info

endpointOf is NOT duplicated with public-v1-telemetry.ts

packages/api/src/middleware/internal-auth.ts

The public mount uses the ts-rest route KEY from contract metadata — never the raw URL. endpointOf exists specifically because the internal mount has no metadata registry. The two approaches are genuinely different; no extraction is warranted.

History · 47 commits

  1. 82bb5b9blockedincremental5H · 5M · 4L2026-08-12 01:48
  2. 90aa3d5needs attentionincremental1H · 5M · 3L2026-08-11 19:37
  3. 29d19a0needs attentionincremental1H · 5M · 9L2026-08-11 17:41
  4. 9bd8a0cneeds attentionfull0H · 5M · 9L2026-08-11 02:14
  5. 62ec3f7needs attentionincremental2H · 5M · 6L2026-08-10 22:51
  6. f93bca9needs attentionincremental2H · 5M · 8L2026-08-10 17:51
  7. 052db6fneeds attentionincremental1H · 3M · 4L2026-08-09 21:13
  8. 45699caneeds attentionincremental0H · 7M · 11L2026-08-09 17:44
  9. b843d8aneeds attentionincremental1H · 7M · 9L2026-08-09 04:05
  10. e1757b8needs attentionincremental0H · 3M · 6L2026-08-05 02:11
  11. 7a762faneeds attentionincremental2H · 5M · 5L2026-08-05 01:25
  12. 3300a60needs attentionincremental2H · 4M · 7L2026-08-04 19:06
  13. 0c8a7f5needs attentionincremental0H · 4M · 9L2026-08-04 18:15
  14. 345f42eneeds attentionincremental2H · 6M · 9L2026-08-04 17:28
  15. 8338a9aneeds attentionincremental5H · 14M · 14L2026-08-04 00:33
  16. 41be4c3needs attentionincremental0H · 5M · 7L2026-08-03 23:49
  17. 5ed593dneeds attentionincremental1H · 6M · 6L2026-08-03 21:32
  18. b333e25needs attentionincremental4H · 9M · 8L2026-08-03 21:00
  19. 5642cccneeds attentionincremental2H · 3M · 2L2026-08-03 20:17
  20. 73b0b39needs attentionincremental3H · 10M · 13L2026-07-31 18:29
  21. b19852eneeds attentionincremental0H · 1M · 5L2026-07-29 05:04current
  22. 3845205needs attentionincremental3H · 6M · 4L2026-07-29 04:47
  23. eb8eb50needs attentionincremental0H · 1M · 2L2026-07-29 03:03
  24. f4720a3needs attentionincremental6H · 8M · 7L2026-07-29 02:54
  25. f8d341ablockedincremental2H · 2M · 5L2026-07-29 00:00
  26. a7f1a64needs attentionincremental2H · 8M · 8L2026-07-28 18:41
  27. 738b60bblockedincremental3H · 6M · 5L2026-07-28 00:46
  28. 2c248b6needs attentionincremental8H · 12M · 8L2026-07-27 23:23
  29. 1346cc0needs attentionincremental2H · 8M · 6L2026-07-27 20:15
  30. 0716018needs attentionincremental2H · 11M · 12L2026-07-27 19:22
  31. 215cd2dneeds attentionincremental3H · 6M · 5L2026-07-27 17:04
  32. ec46958needs attentionincremental0H · 3M · 5L2026-07-27 16:51
  33. de7b337blockedincremental4H · 9M · 14L2026-07-27 06:36
  34. b1bb9c0needs attentionincremental1H · 2M · 4L2026-07-27 05:09
  35. 4701d11needs attentionincremental0H · 4M · 3L2026-07-27 04:44
  36. e1626c4needs attentionincremental3H · 9M · 10L2026-07-27 03:21
  37. 195f198needs attentionincremental3H · 3M · 3L2026-07-25 01:22
  38. 42c7358safeincremental0H · 0M · 0L2026-07-22 20:46
  39. 85b9018needs attentionincremental0H · 1M · 6L2026-07-21 23:51
  40. a7b2a9aneeds attentionincremental0H · 9M · 12L2026-07-21 18:49
  41. c2ee0daneeds attentionincremental4H · 7M · 7L2026-07-21 02:17
  42. e8ffa5eneeds attentionincremental4H · 7M · 5L2026-07-21 01:33
  43. a2d2a54needs attentionincremental2H · 7M · 3L2026-07-21 00:51
  44. 576fbd6needs attentionfull1H · 6M · 7L2026-07-21 00:35
  45. d3465e8needs attentionincremental1H · 7M · 10L2026-07-21 00:23
  46. dc794a7needs attentionincremental0H · 5M · 5L2026-07-20 23:46
  47. 9082773needs attentionfull1H · 3M · 3L2026-07-20 23:13