Skip to content

fix(keys): register :reveal/:enable/:renew before generic CRUD#1155

Merged
ding113 merged 2 commits into
devfrom
fix/keys-reveal-route-order
May 4, 2026
Merged

fix(keys): register :reveal/:enable/:renew before generic CRUD#1155
ding113 merged 2 commits into
devfrom
fix/keys-reveal-route-order

Conversation

@ding113

@ding113 ding113 commented May 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • Reorder Hono route registrations in src/app/api/v1/resources/keys/router.ts so the custom-method routes (/keys/{id}:reveal, :enable, :renew) register before the generic GET/PATCH/DELETE /keys/{keyId} block.
  • Add the missing integration test for GET /api/v1/keys/{id}:reveal in tests/api/v1/keys/keys.test.ts (plus an OpenAPI doc assertion). The pre-existing keys.crud.test.ts only string-matches the test file via readFileSync, so it could not catch this regression.
  • Regenerate src/lib/api-client/v1/openapi-types.gen.ts to reflect the path ordering — content unchanged, only path-block order shifts.

Related Issues & PRs

Root Cause

Reproduces GET /api/v1/keys/155:reveal → 400 request.validation_failed, keyId expected number received NaN on the user management page.

Hono's RegExpRouter resolves overlapping matches in registration order. With the generic GET /keys/{keyId} registered first via keysRouter.openapi(...), its auto-Zod-validating middleware captured keyId="155:reveal", z.coerce.number() produced NaN, and the defaultHook returned the 400 the user saw. The specific /keys/:keyId{[0-9]+:reveal} route registered later was never hit, so the suffix-stripping logic in parseKeyParams never ran.

POST /keys/{id}:enable and :renew are not currently affected only because the generic CRUD routes use GET/PATCH/DELETE — moving them up too defends against a future generic POST route silently re-introducing the same bug.

Solution

Reordered route registrations to ensure custom-method routes with colon-suffixes (:reveal, :enable, :renew) are registered before the generic /keys/{keyId} CRUD routes. This ensures Hono's RegExpRouter matches the more specific patterns first.

Changes

Core Fix

  • src/app/api/v1/resources/keys/router.ts — Moved custom-method route registrations (enable, renew, reveal) above generic CRUD routes (GET/PATCH/DELETE /keys/{keyId})

Test Coverage

  • tests/api/v1/keys/keys.test.ts — Added integration test for GET /api/v1/keys/{id}:reveal endpoint with cache-control header assertions

Generated Types

  • src/lib/api-client/v1/openapi-types.gen.ts — Regenerated to reflect path ordering changes (no functional changes)

Test plan

  • bunx vitest run tests/api/v1/keys/keys.test.ts — 11/11 pass, includes new reveals unmasked key value with no-store headers case.
  • bun run typecheck — clean.
  • bun run lint — no new warnings (8 pre-existing, all in unrelated files).
  • bun run openapi:check — generated types in sync.
  • bun run openapi:lint — passes.
  • bun run build — production build succeeds.
  • Manual: bun run dev, click copy/eye on a key in user management — no 400 toast, key is revealed/copied.

Description enhanced with related issue links

Greptile Summary

Route ordering regression fix: custom-method routes (:reveal, :enable, :renew) now register before the generic CRUD block, preventing Hono's RegExpRouter from capturing colon-suffixed paths and failing Zod coercion. Also adds a missing integration test for the reveal endpoint, simplifies the expiresAt Zod union type, and regenerates OpenAPI types.

Confidence Score: 5/5

Safe to merge — targeted route-ordering fix with matching test coverage and no logic regressions.

No P0 or P1 findings. The route reordering is the correct solution for Hono's RegExpRouter behaviour, the schema change is semantically equivalent, the generated types are in sync, and the new integration test directly exercises the repaired code path.

No files require special attention.

Important Files Changed

Filename Overview
src/app/api/v1/resources/keys/router.ts Core fix: moves :enable, :renew, :reveal registrations before the generic GET/PATCH/DELETE /keys/{keyId} block to prevent RegExpRouter from capturing colon-suffixes as NaN keyIds.
src/lib/api/v1/schemas/keys.ts Clean-up: expiresAt changed from z.union([z.string(), z.null()]).optional() to z.string().nullable().optional() (semantically equivalent), and KeyRevealResponseSchema description updated to mention key-owner access.
tests/api/v1/keys/keys.test.ts Adds getUnmaskedKeyMock and a new integration test for GET /api/v1/keys/10:reveal, asserting correct status, response body, Cache-Control: no-store, Pragma: no-cache, and mock invocation with the parsed integer id.
src/lib/api-client/v1/openapi-types.gen.ts Regenerated file: path-block order shifted to match the new router registration order; expiresAt type corrected from string

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant R as Hono RegExpRouter
    participant SR as Specific route (colon-suffix pattern)
    participant GR as Generic route /keys/{keyId}
    participant H as Handler

    Note over R: Before fix
    C->>R: GET /keys/155:reveal
    R->>GR: Matched generic route first
    GR-->>C: 400 - NaN coercion failure

    Note over R: After fix
    C->>R: GET /keys/155:reveal
    R->>SR: Matched specific route first
    SR->>H: Strips suffix, parses id
    H-->>C: 200 with unmasked value
Loading

Reviews (2): Last reviewed commit: "fix(keys): address PR review on schema a..." | Re-trigger Greptile

… 400 NaN

Hono's RegExpRouter resolves overlapping route matches in registration order.
The generic `GET /keys/{keyId}` registered via `keysRouter.openapi(...)`
auto-installs a Zod-validating middleware; with the more specific
`/keys/:keyId{[0-9]+:reveal}` registered AFTER it, every reveal request
matched the generic route first, captured `keyId="155:reveal"`, and was
rejected as `expected number, received NaN` before ever reaching `revealKey`.
The handler-level suffix strip in `parseKeyParams` was correct but never ran.

Move the three custom-method routes (`:enable`, `:renew`, `:reveal`) ahead
of the generic GET/PATCH/DELETE block. Add a route-level integration test
for `:reveal` (the gap that let this regression slip through) and an
OpenAPI doc assertion. Regenerated `openapi-types.gen.ts` reflects only the
path-ordering change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

重新排列密钥路由注册顺序,使带方法后缀的路由(:enable:renew:reveal)先注册以避免路由匹配误捕获;同时更新/再生成 OpenAPI 类型以包含新的 reveal/enable/renew 操作,调整部分字段类型与描述,并新增针对 reveal 的测试和 mock。

Changes

密钥API路由与类型更新

Layer / File(s) Summary
路由注册顺序
src/app/api/v1/resources/keys/router.ts
将 admin CRUD 路由(GET/PATCH/DELETE /keys/{keyId})从之前的位置移至自定义后缀路由(:enable:renew:reveal)之后;添加注释说明顺序原因以避免 Hono 路由匹配将 "155:reveal" 之类值误识别为数字 keyId 从而导致 Zod 强制失败。
数据/类型(生成)
src/lib/api-client/v1/openapi-types.gen.ts
重新生成 OpenAPI 类型:新增/更新操作类型 postKeysByKeyidEnable(可选 X-CCH-CSRF 头,{ enabled: boolean } 请求体)、postKeysByKeyidRenew{ expiresAt: string; enableKey?: boolean } 请求体)、getKeysByKeyidReveal(200 响应 { key: string });更新 patchKeysByKeyid/deleteKeysByKeyid 等的请求/响应建模,expiresAt 类型改为可为 `string
模式/验证
src/lib/api/v1/schemas/keys.ts
KeyMutationFields.expiresAt 从 union 改为 z.string().nullable().optional()KeyRevealResponseSchema.key 描述扩展为对管理员和密钥所有者返回未掩码值,非所有者返回 403。
测试 / 文档断言
tests/api/v1/keys/keys.test.ts
新增 getUnmaskedKey mock;添加 GET /api/v1/keys/{keyId}:reveal 测试,断言返回未掩码 key、响应无缓存头并验证 mock 调用;扩展 OpenAPI 断言包含 :reveal 路径。

预估代码审查工作量

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰准确地概括了主要变更:重新排列 Hono 路由注册顺序,将自定义方法路由(:reveal/:enable/:renew)放在通用 CRUD 路由之前。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed 拉取请求描述详细说明了路由重新排序的原因、根本原因和解决方案,与变更集完全相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/keys-reveal-route-order

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added bug Something isn't working area:core javascript Pull requests that update javascript code size/M Medium PR (< 500 lines) labels May 4, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request reorders the API routes in the keys router to ensure that custom-method routes, such as :reveal, are registered before generic CRUD routes. This change prevents Hono's RegExpRouter from incorrectly capturing custom methods as part of the keyId parameter. Additionally, the PR introduces a new test case for the :reveal endpoint to verify its response status, JSON output, and security headers, while also updating the OpenAPI documentation checks. I have no feedback to provide.

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

No significant issues identified in this PR.

PR Size: M

  • Lines changed: 401
  • Files changed: 3

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Automated review by Codex AI

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Reviewed PR #1155 on branch fix/keys-reveal-route-order.
  • Computed PR size as M (401 lines changed across 3 files) and applied label size/M.
  • Completed 6-perspective diff review of the changed files and submitted a PR review comment; no diff-line issues met the reporting threshold, so no inline comments were posted.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/api-client/v1/openapi-types.gen.ts`:
- Around line 33196-33197: Update the JSDoc for the "key" property (the Unmasked
key value in openapi-types.gen.ts) to accurately state that the unmasked key is
returned to admins and to the key's owner (not only admins); reference the
authorization behavior implemented by getUnmaskedKey in src/actions/keys.ts and
the keys router in src/app/api/v1/resources/keys/router.ts to ensure the
description matches the 403 behavior for non-owner/non-admin callers.
- Line 33715: The generated OpenAPI type for expiresAt is wrong because union
with unknown collapses to unknown; update the OpenAPI type generator logic (the
code that maps Zod unions to TS unions) so that z.union([z.string(), z.null()])
yields "string | null" instead of "string | unknown" (fix the mapping for Zod's
null union case used by the keys schema that produces expiresAt). Also update
the JSDoc text that currently reads "Returned only to admin callers" (near the
JSDoc for the keys endpoint in openapi-types.gen.ts) to reflect the actual
access rules (admins and key owners) as implemented in the router
(src/app/api/v1/resources/keys/router.ts).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0782ca69-3ea1-463a-be57-a5fede778b2e

📥 Commits

Reviewing files that changed from the base of the PR and between 6cf7e44 and f7a537c.

📒 Files selected for processing (3)
  • src/app/api/v1/resources/keys/router.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • tests/api/v1/keys/keys.test.ts

Comment thread src/lib/api-client/v1/openapi-types.gen.ts Outdated
Comment thread src/lib/api-client/v1/openapi-types.gen.ts Outdated
- expiresAt: switch from `z.union([z.string(), z.null()])` to
  `z.string().nullable()` so the generated TS type is `string | null`
  instead of `string | unknown` (which collapses to `unknown` and
  silently disables type checking on the field).
- KeyRevealResponseSchema.key: correct the description — admins AND the
  key's owner can reveal; non-owners receive 403. The previous
  "Returned only to admin callers" was misleading consumers.

Regenerated openapi-types.gen.ts to reflect both source changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

@ding113 ding113 merged commit 0c58703 into dev May 4, 2026
11 checks passed
@ding113 ding113 deleted the fix/keys-reveal-route-order branch May 4, 2026 11:20
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap May 4, 2026
@github-actions github-actions Bot mentioned this pull request May 13, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core bug Something isn't working javascript Pull requests that update javascript code size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant