Skip to content

fix: scope my-usage ip geo lookups#1034

Merged
ding113 merged 4 commits into
devfrom
fix/my-usage-ip-geo-auth
Apr 19, 2026
Merged

fix: scope my-usage ip geo lookups#1034
ding113 merged 4 commits into
devfrom
fix/my-usage-ip-geo-auth

Conversation

@ding113

@ding113 ding113 commented Apr 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a my-usage-specific IP geo action (getMyIpGeoDetails) gated by the current key's visible usage logs
  • Keep /api/ip-geo/[ip] admin-only and route my-usage IP dialogs through the scoped action
  • Cover the new behavior with unit and API integrity tests, plus my-usage component wiring

Problem

PR #1027 introduced IP geolocation lookup via the admin-only /api/ip-geo/[ip] endpoint. PR #1029 then reused VirtualizedLogsTable in the my-usage page but left IP detail lookups disconnected (the detail dialog was disabled). Readonly/API-only key users had no way to view IP geo details for their own usage logs, while the admin endpoint was not suitable for non-admin access.

Related PRs:

Solution

Introduce a new server action getMyIpGeoDetails in the my-usage module that:

  1. Authenticates the session with allowReadOnlyAccess: true
  2. Verifies the requested IP actually appears in the current key's non-deleted usage logs (message_request table)
  3. Checks that IP geo lookup is enabled in system settings
  4. Performs the lookup via the existing lookupIp client

The useIpGeo hook gains a mode parameter ("default" | "my-usage") that routes my-usage requests to the new scoped action instead of the admin endpoint. The IpDetailsDialog and VirtualizedLogsTable components propagate this mode through props.

Changes

File Change
src/actions/my-usage.ts Add getMyIpGeoDetails action with key-scoped IP verification (+56)
src/hooks/use-ip-geo.ts Add IpGeoLookupMode type; route "my-usage" mode through scoped action (+26/-2)
src/app/[locale]/dashboard/_components/ip-details-dialog.tsx Accept and forward lookupMode prop (+11/-5)
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx Accept and forward ipLookupMode prop (+10/-1)
src/app/[locale]/my-usage/_components/usage-logs-section.tsx Pass ipLookupMode="my-usage" to table (+1)
src/app/api/actions/[...route]/route.ts Register getMyIpGeoDetails OpenAPI route with allowReadOnlyAccess (+24)
tests/unit/actions/my-usage-ip-geo.test.ts New: 5 unit tests covering auth, validation, scope, and disabled scenarios (+138)
tests/api/my-usage-readonly.test.ts New: integration test verifying key-scoped IP visibility (+71)
tests/api/api-actions-integrity.test.ts Register new endpoint in integrity checks (+2/-1)
src/app/[locale]/my-usage/_components/usage-logs-section.test.tsx Assert ipLookupMode="my-usage" is passed through (+3)

Security

The action enforces that only IPs appearing in the requesting key's own non-deleted message_request rows can be queried. A readonly key cannot probe arbitrary IPs or discover IPs from other keys' traffic.

Verification

  • bun run build
  • bun run lint
  • bun run lint:fix
  • bun run typecheck
  • bun run test
  • bunx vitest run tests/unit/actions/my-usage-ip-geo.test.ts 'src/app/[locale]/my-usage/_components/usage-logs-section.test.tsx' tests/api/api-actions-integrity.test.ts 'src/app/[locale]/dashboard/_components/ip-details-dialog.test.tsx'
  • bunx vitest run --config tests/configs/integration.config.ts tests/api/my-usage-readonly.test.ts (fails locally in this environment because DSN is not set; existing DB-backed tests in that file cannot run without DB env)

Description enhanced by Claude AI

Greptile Summary

This PR closes the access gap left by #1029 by introducing a key-scoped getMyIpGeoDetails server action that lets readonly/API-only users look up IP geo details for IPs that appear in their own usage logs, while keeping the admin /api/ip-geo/[ip] endpoint off-limits to non-admins. The scope check — verifying the requested IP appears in non-deleted, non-warmup message_request rows for the current key — is well-structured and the prop-threading through VirtualizedLogsTableIpDetailsDialoguseIpGeo is clean.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style suggestions that do not affect correctness or security

The security model is sound: IP lookups are gated behind session auth, a key-scoped DB existence check, and the global feature flag. The usageLedger.requestId column is NOT NULL so the ledger-only fallback is correct. The disableDetailDialog + ipLookupMode combination is intentional (they gate different dialogs). Unit and integration test coverage is thorough. The two flagged items are a reversed check order (dead code) and a minor filter inconsistency in the ledger fallback — neither affects production behavior.

src/hooks/use-ip-geo.ts (check order) and src/actions/my-usage.ts (ledger fallback warmup filter) are worth a second look, but neither blocks merge

Important Files Changed

Filename Overview
src/actions/my-usage.ts Adds getMyIpGeoDetails server action with key-scoped IP verification; minor inconsistency in ledger-only fallback path (no warmup/deleted-at filter)
src/hooks/use-ip-geo.ts Adds IpGeoLookupMode and routes my-usage requests through scoped action; response.ok guard is dead code in practice due to reversed check order
src/app/[locale]/dashboard/_components/ip-details-dialog.tsx Cleanly threads lookupMode prop through to useIpGeo; no issues
src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx Adds ipLookupMode prop forwarded to IpDetailsDialog independently of disableDetailDialog; wiring is correct
src/app/[locale]/my-usage/_components/usage-logs-section.tsx Passes ipLookupMode="my-usage" alongside disableDetailDialog (which only gates the error/model dialog, not IP dialog); correct usage
tests/unit/actions/my-usage-ip-geo.test.ts Well-structured unit tests covering auth, empty-IP, disabled-feature, scope, readonly, ledger-only, and privacy-preserving log output cases
tests/api/my-usage-readonly.test.ts Integration test correctly asserts scope (not denied by scoping) vs. cross-key IP (denied), addressing the prior thread concern about feature-flag coupling
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/hooks/use-ip-geo.ts
Line: 36-38

Comment:
**Dead `response.ok` guard after `result.ok` check**

`response.ok` is checked on line 36 only after `result.ok` already returned on line 32. In practice this branch is unreachable: the server returns HTTP 4xx when `ok` is `false` and HTTP 200 when `ok` is `true`, so a combination of `result.ok === true` and `!response.ok` never occurs. A non-JSON 5xx response would throw at `response.json()` before either check runs.

The `default` mode guard (line 45) checks `response.ok` *before* parsing JSON, which is the more conventional order. Consider aligning the two paths:

```suggestion
        if (!response.ok) {
          return { status: "error", error: `my-ip-geo fetch failed: ${response.status}` };
        }

        const result = (await response.json()) as ActionResult<IpGeoLookupResponse>;
        if (!result.ok) {
          return { status: "error", error: result.error ?? "my-ip-geo fetch failed" };
        }

        return result.data;
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/actions/my-usage.ts
Line: 735-742

Comment:
**Ledger-only fallback skips warmup and deleted-at filters**

The primary `messageRequest` query applies both `isNull(messageRequest.deletedAt)` and `EXCLUDE_WARMUP_CONDITION`, but the ledger-only fallback on these lines applies neither. In ledger-only mode an IP that appeared *only* in warmup or soft-deleted requests would still pass the scope check via the ledger, while it would be rejected in normal mode.

The impact is minor (the IP is still scoped to the key's own ledger rows), but the inconsistency could surprise future maintainers. Adding a warmup exclusion filter here keeps the two paths in sync with the user-visible "my-usage" log semantics.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (4): Last reviewed commit: "fix: align my-usage ip visibility checks" | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

新增只读受限服务器动作 getMyIpGeoDetails 并注册到 OpenAPI;扩展 useIpGeo 支持 "my-usage" 模式通过新动作获取 IP 地理信息;将 lookup 模式向相关 UI 组件(表格、详情弹窗)传递;新增/更新多项单元与集成测试覆盖授权、校验与路由注册。

Changes

Cohort / File(s) Summary
服务器动作与路由
src/actions/my-usage.ts, src/app/api/actions/[...route]/route.ts
新增导出异步动作 getMyIpGeoDetails:会话只读验证、参数修剪/校验、系统设置开关、基于 message_request.clientIp(并在 ledger-only 模式下回退 usage_ledger)的可见性检查、调用 lookupIp 并按结果返回;在路由注册中新增对应 OpenAPI 动作端点与请求/响应模式。
客户端钩子
src/hooks/use-ip-geo.ts
新增类型 `IpGeoLookupMode = "default"
UI 组件与表格集成
src/app/[locale]/dashboard/_components/ip-details-dialog.tsx, src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx, src/app/[locale]/my-usage/_components/usage-logs-section.tsx
IpDetailsDialog 增加可选 lookupMode?: IpGeoLookupMode 并向内容传递;VirtualizedLogsTable 增加可选 ipLookupMode?: IpGeoLookupMode(默认 "default")并传递到 IpDetailsDialogUsageLogsSectionipLookupMode="my-usage" 调用表格。
测试与验证
tests/unit/actions/my-usage-ip-geo.test.ts, tests/api/my-usage-readonly.test.ts, tests/api/api-actions-integrity.test.ts, src/app/[locale]/my-usage/_components/usage-logs-section.test.tsx
新增单元测试覆盖 getMyIpGeoDetails 的认证、空输入、可见性检查、设置关闭、ledger-only 回退与成功路径;集成测试断言新 OpenAPI 路径已注册并验证只读 key 隔离行为;组件测试更新 mock 以断言 ipLookupMode="my-usage" 被传递。

估算代码审查工作量

🎯 3 (Moderate) | ⏱️ ~22 minutes

可能相关的 PR

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确概括了 PR 的核心变更:为 my-usage 页面引入作用域限制的 IP 地理位置查询功能,这是整个 PR 的主要目标。
Description check ✅ Passed PR 描述详细说明了问题、解决方案、变更的文件列表、安全考虑和验证步骤,与变更集完全相关且信息完整。

✏️ 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/my-usage-ip-geo-auth
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch fix/my-usage-ip-geo-auth

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 size/M Medium PR (< 500 lines) labels Apr 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@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 introduces a restricted IP geolocation lookup feature for the usage logs. It adds a new server action, getMyIpGeoDetails, which ensures that users can only access geolocation data for IPs that have appeared in their own usage history. The useIpGeo hook and UI components like IpDetailsDialog and VirtualizedLogsTable were updated to support a lookupMode property for routing these requests. Feedback was provided to simplify the return type of the new server action by utilizing the existing IpGeoLookupResponse type.

Comment thread src/actions/my-usage.ts
type UsageLogSummary,
type UsageLogsBatchResult,
} from "@/repository/usage-logs";
import type { IpGeoLookupResult, IpGeoPrivateMarker } from "@/types/ip-geo";

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.

medium

Consider importing IpGeoLookupResponse as well to simplify the return type definition of getMyIpGeoDetails.

Suggested change
import type { IpGeoLookupResult, IpGeoPrivateMarker } from "@/types/ip-geo";
import type { IpGeoLookupResponse, IpGeoLookupResult, IpGeoPrivateMarker } from "@/types/ip-geo";

Comment thread src/actions/my-usage.ts
Comment on lines +699 to +705
export async function getMyIpGeoDetails(params: { ip: string; lang?: string }): Promise<
ActionResult<{
status: "ok" | "private" | "error";
data?: IpGeoLookupResult | IpGeoPrivateMarker;
error?: string;
}>
> {

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.

medium

The return type of getMyIpGeoDetails can be simplified by using the IpGeoLookupResponse type directly, which already represents the union of successful, private, and error states for IP geolocation lookups.

export async function getMyIpGeoDetails(params: { ip: string; lang?: string }): Promise<
  ActionResult<IpGeoLookupResponse>
> {

Comment thread tests/api/my-usage-readonly.test.ts Outdated
Comment on lines +350 to +352
expect(visible.response.status).toBe(200);
expect(visible.json).toMatchObject({ ok: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Visible-IP assertion couples scoping test to ipGeoLookupEnabled setting

expect(visible.json).toMatchObject({ ok: true }) passes only when ipGeoLookupEnabled is true in the test DB and the upstream geo service responds without error. If the feature flag is off (common in a fresh test DB), getMyIpGeoDetails returns { ok: false, error: "IP geolocation disabled" } — causing the assertion to fail even though the scope check itself is correct.

The test's primary goal is to verify scoping (Key A can look up its own IP; Key B's IP is denied). Consider asserting on the error string rather than ok: true so the test is independent of the geo-lookup feature flag:

// Assert that the visible IP is not blocked by scoping:
expect(visible.json).not.toMatchObject({
  ok: false,
  error: "IP not found in current key usage logs",
});
// Assert the hidden IP IS blocked by scoping:
expect(hidden.json).toMatchObject({
  ok: false,
  error: "IP not found in current key usage logs",
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/api/my-usage-readonly.test.ts
Line: 350-352

Comment:
**Visible-IP assertion couples scoping test to `ipGeoLookupEnabled` setting**

`expect(visible.json).toMatchObject({ ok: true })` passes only when `ipGeoLookupEnabled` is `true` in the test DB *and* the upstream geo service responds without error. If the feature flag is off (common in a fresh test DB), `getMyIpGeoDetails` returns `{ ok: false, error: "IP geolocation disabled" }` — causing the assertion to fail even though the scope check itself is correct.

The test's primary goal is to verify scoping (Key A can look up its own IP; Key B's IP is denied). Consider asserting on the error string rather than `ok: true` so the test is independent of the geo-lookup feature flag:

```typescript
// Assert that the visible IP is not blocked by scoping:
expect(visible.json).not.toMatchObject({
  ok: false,
  error: "IP not found in current key usage logs",
});
// Assert the hidden IP IS blocked by scoping:
expect(hidden.json).toMatchObject({
  ok: false,
  error: "IP not found in current key usage logs",
});
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread tests/api/my-usage-readonly.test.ts Outdated
body: { ip: visibleIp, lang: "en" },
});
expect(visible.response.status).toBe(200);
expect(visible.json).toMatchObject({ ok: true });

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.

[Medium] [TEST-BRITTLE] Visible-IP assertion couples scoping test to ipGeoLookupEnabled setting

Why this is a problem: The test name states it verifies key-scoped IP access, but expect(visible.json).toMatchObject({ ok: true }) also depends on (1) ipGeoLookupEnabled being true in system settings and (2) the upstream geo service returning successfully. If the feature flag is off in the test DB, the action returns { ok: false, error: "IP geolocation disabled" } and the assertion fails -- even though the scoping logic itself is correct.

Suggested fix:

// Assert the visible IP is NOT blocked by scoping (the actual concern of this test).
// This passes regardless of ipGeoLookupEnabled or upstream service status.
expect(visible.json).not.toMatchObject({
  ok: false,
  error: "IP not found in current key usage logs",
});

// Assert the hidden IP IS blocked by scoping (unchanged).
expect(hidden.json).toMatchObject({
  ok: false,
  error: "IP not found in current key usage logs",
});

@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

This PR introduces a scoped getMyIpGeoDetails server action for the my-usage page, with proper key-scoped IP verification, readonly access support, and adequate unit test coverage. The implementation is architecturally sound -- the scope check correctly gates lookups to IPs that appear in the requesting key's own non-deleted usage logs, preventing cross-key IP enumeration. Component prop threading through IpDetailsDialog and VirtualizedLogsTable is clean and backward-compatible via defaults.

PR Size: M

  • Lines changed: 351
  • Files changed: 10

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 1 0
Simplification 0 0 0 0

Medium Priority Issues (Should Fix)

  • [TEST-BRITTLE] tests/api/my-usage-readonly.test.ts:351 -- The visible-IP assertion expect(visible.json).toMatchObject({ ok: true }) couples the scoping test to ipGeoLookupEnabled being true and the upstream geo service being available. The test's stated purpose is verifying key-scoped IP access control, so the assertion should verify that scoping does not block the visible IP (using not.toMatchObject with the scope-error message), not that the full geo lookup succeeds.

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10) -- parameterized queries, no injection risk, proper scope gating
  • Error handling -- try/catch with logging, user-facing error messages
  • Type safety -- IpGeoLookupMode union type, ActionResult generic properly typed
  • Documentation accuracy
  • Test coverage -- 5 unit tests + 1 integration test covering auth, validation, scope, and settings
  • Code clarity

Automated review by Claude AI

Comment thread tests/api/my-usage-readonly.test.ts Outdated
authToken: keyA.key,
body: { ip: hiddenIp, lang: "en" },
});
expect(hidden.response.status).toBe(200);

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.

[High] [TEST-MISSING-CRITICAL] Wrong HTTP status assertion for ActionResult ok:false

Why this is a problem: The Actions adapter maps { ok: false, error: ... } to HTTP 400 (business error): return c.json({ ok: false, error: result.error, ... }, 400); (src/lib/api/action-adapter-openapi.ts). As written, tests/api/my-usage-readonly.test.ts:359 expects 200 and will fail.

Suggested fix:

expect(hidden.response.status).toBe(400);
expect(hidden.json).toMatchObject({
  ok: false,
  error: "IP not found in current key usage logs",
});

Comment thread src/hooks/use-ip-geo.ts Outdated
headers: { "content-type": "application/json" },
body: JSON.stringify({ ip, lang: locale }),
});
if (!response.ok) throw new Error(`my-ip-geo fetch failed: ${response.status}`);

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.

[High] [LOGIC-BUG] useIpGeo my-usage branch drops ActionResult error details on HTTP 400

Why this is a problem: /api/actions/* returns HTTP 400 for { ok: false, error: ... } results, but src/hooks/use-ip-geo.ts:30 throws on !response.ok before parsing the JSON body. That makes the later if (!result.ok) block unreachable for business errors and loses the server-provided error message.

Suggested fix (parse the ActionResult even on 400 and normalize into IpGeoLookupResponse so the dialog can render details):

const response = await fetch("/api/actions/my-usage/getMyIpGeoDetails", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ ip, lang: locale }),
});

const result = (await response.json()) as ActionResult<IpGeoLookupResponse>;
if (!result.ok) {
  return { status: "error", error: result.error };
}

return result.data;

@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

PR adds a my-usage-scoped IP geolocation lookup action and wires the IP details dialog to use it from the my-usage logs table. The remaining issues are around Actions adapter HTTP semantics (400 vs 200) not matching the new test and the client hook.

PR Size: M

  • Lines changed: 351
  • Files changed: 10

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 1 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 1 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

None.

High Priority Issues (Should Fix)

  • tests/api/my-usage-readonly.test.ts:359: asserts HTTP 200 for { ok: false } ActionResult, but the Actions adapter returns HTTP 400 for business errors.
  • src/hooks/use-ip-geo.ts:30: throws on !response.ok before parsing the ActionResult JSON, dropping server error details on HTTP 400.

Review Coverage

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

Automated review by Codex AI

@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: 3

🤖 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/actions/my-usage.ts`:
- Around line 713-733: Move the ip-lookup feature flag check before the DB query
and make the visibility filter consistent with my-usage by adding the warmup
exclusion: first call getSystemSettings() and return when
settings.ipGeoLookupEnabled is false, then run the select on messageRequest;
when building the where clause for the query (currently using
isNull(messageRequest.deletedAt), eq(messageRequest.key, session.key.key),
eq(messageRequest.clientIp, ip)) also include the EXCLUDE_WARMUP_CONDITION used
elsewhere (e.g., in getMyStatsSummary) so IPs only from warmup requests are
excluded and the visibility semantics match the my-usage list.
- Around line 699-705: The return type of getMyIpGeoDetails should use the
existing discriminated union IpGeoLookupResponse instead of the inline type;
update the function signature from Promise<ActionResult<{ status: "ok" |
"private" | "error"; ... }>> to Promise<ActionResult<IpGeoLookupResponse>> and
adjust any imports/exports as needed so IpGeoLookupResponse is available (this
matches the type returned by lookupIp()).

In `@src/hooks/use-ip-geo.ts`:
- Around line 24-37: The my-usage branch in use-ip-geo.ts currently throws new
Error(result.error) when ActionResult<IpGeoLookupResponse> has ok: false, which
yields an "undefined" message if result.error is missing; change the error throw
to include a safe fallback message and/or include serialized result so the Error
always has useful text (e.g. use result.error ?? "Unknown IP geo lookup error"
or include JSON.stringify(result) when constructing the Error) — update the code
paths that reference result (the constant named result inside the mode ===
"my-usage" block) so any failure throws a descriptive Error instead of
undefined.
🪄 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: 91386831-c8d9-4134-8c20-b5aef4115c56

📥 Commits

Reviewing files that changed from the base of the PR and between b991c3a and 97bc73b.

📒 Files selected for processing (10)
  • src/actions/my-usage.ts
  • src/app/[locale]/dashboard/_components/ip-details-dialog.tsx
  • src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx
  • src/app/[locale]/my-usage/_components/usage-logs-section.test.tsx
  • src/app/[locale]/my-usage/_components/usage-logs-section.tsx
  • src/app/api/actions/[...route]/route.ts
  • src/hooks/use-ip-geo.ts
  • tests/api/api-actions-integrity.test.ts
  • tests/api/my-usage-readonly.test.ts
  • tests/unit/actions/my-usage-ip-geo.test.ts

Comment thread src/actions/my-usage.ts
Comment on lines +699 to +705
export async function getMyIpGeoDetails(params: { ip: string; lang?: string }): Promise<
ActionResult<{
status: "ok" | "private" | "error";
data?: IpGeoLookupResult | IpGeoPrivateMarker;
error?: string;
}>
> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP -C2 '\b(IpGeoLookupResult|IpGeoPrivateMarker)\b' --type=ts
ast-grep --pattern 'export function lookupIp($$$) { $$$ }'
ast-grep --pattern 'export async function lookupIp($$$) { $$$ }'

Repository: ding113/claude-code-hub

Length of output: 13458


🏁 Script executed:

# Check ActionResult type definition
rg -nP 'export (type|interface)\s+ActionResult' --type=ts

# Check the implementation of getMyIpGeoDetails
sed -n '699,750p' src/actions/my-usage.ts

# Check if IpGeoLookupResponse is used elsewhere
rg -nP 'IpGeoLookupResponse' --type=ts

Repository: ding113/claude-code-hub

Length of output: 3594


返回类型应改用已有的 IpGeoLookupResponse,而非 IpGeoLookupResult

当前内联的 { status: "ok" | "private" | "error"; data?: ...; error?: ... } 与实现中 lookupIp() 返回的已有类型 IpGeoLookupResponse 重复。后者已是正确的判别联合,完整包含三个分支:{ status: "ok"; data: IpGeoLookupResult } | { status: "private"; data: IpGeoPrivateMarker } | { status: "error"; error: string }。应直接使用 ActionResult<IpGeoLookupResponse> 替代内联定义。

♻️ 建议修改
-export async function getMyIpGeoDetails(params: { ip: string; lang?: string }): Promise<
-  ActionResult<{
-    status: "ok" | "private" | "error";
-    data?: IpGeoLookupResult | IpGeoPrivateMarker;
-    error?: string;
-  }>
-> {
+export async function getMyIpGeoDetails(
+  params: { ip: string; lang?: string }
+): Promise<ActionResult<IpGeoLookupResponse>> {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function getMyIpGeoDetails(params: { ip: string; lang?: string }): Promise<
ActionResult<{
status: "ok" | "private" | "error";
data?: IpGeoLookupResult | IpGeoPrivateMarker;
error?: string;
}>
> {
export async function getMyIpGeoDetails(
params: { ip: string; lang?: string }
): Promise<ActionResult<IpGeoLookupResponse>> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/actions/my-usage.ts` around lines 699 - 705, The return type of
getMyIpGeoDetails should use the existing discriminated union
IpGeoLookupResponse instead of the inline type; update the function signature
from Promise<ActionResult<{ status: "ok" | "private" | "error"; ... }>> to
Promise<ActionResult<IpGeoLookupResponse>> and adjust any imports/exports as
needed so IpGeoLookupResponse is available (this matches the type returned by
lookupIp()).

Comment thread src/actions/my-usage.ts
Comment thread src/hooks/use-ip-geo.ts
@github-actions

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.

  • Identified open PR #1034 (“fix: scope my-usage ip geo lookups”) and gathered diff + changed files.
  • Applied size label: size/M (351 lines, 10 files).
  • Posted 2 inline review comments (test status expectation + useIpGeo my-usage error handling).
  • Submitted the required review summary via gh pr review --comment.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3cf1fd93fc

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/actions/my-usage.ts Outdated
Comment on lines +719 to +723
const [visibleLog] = await db
.select({ id: messageRequest.id })
.from(messageRequest)
.where(
and(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Align IP scope check with visible my-usage log sources

The new authorization gate only checks message_request rows (and also applies EXCLUDE_WARMUP_CONDITION), but the my-usage table is populated via getMyUsageLogsBatchFullfindUsageLogsBatch, which can surface rows from other visible sources (notably usage_ledger fallback) and rows not filtered the same way. In those cases, users can see an IP in the table but getMyIpGeoDetails will always return IP not found in current key usage logs, so the IP details dialog fails for visible records.

Useful? React with 👍 / 👎.

@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: 1

♻️ Duplicate comments (1)
tests/api/my-usage-readonly.test.ts (1)

355-365: ⚠️ Potential issue | 🟡 Minor

hidden IP 断言仍依赖 ipGeoLookupEnabled

当前 action 会先检查系统开关;如果测试 DB 中 ipGeoLookupEnabled=false,这里会返回 "IP geolocation disabled",而不是进入 key-scoped not-found 分支。建议在该用例 setup 中显式开启 IP geo,或隔离/mock 系统设置,否则 scoping 断言仍不稳定。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/api/my-usage-readonly.test.ts` around lines 355 - 365, The test's
assertion for the "IP not found in current key usage logs" branch is flaky
because the system feature flag ipGeoLookupEnabled may be false; before invoking
callActionsRoute for /api/actions/my-usage/getMyIpGeoDetails with keyA.key and
hiddenIp you must ensure IP geolocation is enabled in the test setup (e.g. set
ipGeoLookupEnabled = true in the test DB or mock the feature flag) so the action
reaches the key-scoped not-found branch; alternatively explicitly mock the
system setting lookup used by the action to return true prior to calling
callActionsRoute.
🧹 Nitpick comments (1)
tests/api/my-usage-readonly.test.ts (1)

297-297: 确认新增 DB-backed API 测试的目录约定

这个用例属于 API/集成覆盖,但新增在 tests/api/。如果项目没有对 tests/api/ 的例外约定,建议迁移到 tests/integration/,或在规范中明确 API 测试目录。

As per coding guidelines, **/*.test.{ts,tsx}: Unit tests should be located in tests/unit/, integration tests in tests/integration/, and source-adjacent tests in src/**/*.test.ts

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/api/my-usage-readonly.test.ts` at line 297, This test file defines an
integration/API test ("只读 Key:只能查询当前 key 日志里出现过的 IP 详情") placed under tests/api/
which violates the repo's test-directory conventions; move the test file to
tests/integration/ (or update the project test-directory convention if API tests
are intentionally separated) and update any import paths or test runner config
that reference tests/api/my-usage-readonly.test.ts; ensure the test name and
exported test code (the test(...) block) remain unchanged so test discovery
still finds it under tests/integration/.
🤖 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/actions/my-usage.ts`:
- Around line 736-749: The logs in getMyIpGeoDetails are recording raw IPs
(variables ip and params.ip) via logger.warn/logger.error; remove the raw IP
from both log payloads and instead record a non-sensitive identifier (e.g.
params.messageRequestId or session.user.id) or omit the field entirely to avoid
storing personal data. Locate the lookupIp call and the catch block (symbols:
lookupIp, getMyIpGeoDetails, logger.warn, logger.error, ip, params.ip) and
replace the ip/params.ip entries with messageRequestId (if available) or other
internal IDs, or drop them; keep existing error/result details but do not
include the original IP.

---

Duplicate comments:
In `@tests/api/my-usage-readonly.test.ts`:
- Around line 355-365: The test's assertion for the "IP not found in current key
usage logs" branch is flaky because the system feature flag ipGeoLookupEnabled
may be false; before invoking callActionsRoute for
/api/actions/my-usage/getMyIpGeoDetails with keyA.key and hiddenIp you must
ensure IP geolocation is enabled in the test setup (e.g. set ipGeoLookupEnabled
= true in the test DB or mock the feature flag) so the action reaches the
key-scoped not-found branch; alternatively explicitly mock the system setting
lookup used by the action to return true prior to calling callActionsRoute.

---

Nitpick comments:
In `@tests/api/my-usage-readonly.test.ts`:
- Line 297: This test file defines an integration/API test ("只读 Key:只能查询当前 key
日志里出现过的 IP 详情") placed under tests/api/ which violates the repo's test-directory
conventions; move the test file to tests/integration/ (or update the project
test-directory convention if API tests are intentionally separated) and update
any import paths or test runner config that reference
tests/api/my-usage-readonly.test.ts; ensure the test name and exported test code
(the test(...) block) remain unchanged so test discovery still finds it under
tests/integration/.
🪄 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: ced6b177-d734-4561-a97e-985cbf573e23

📥 Commits

Reviewing files that changed from the base of the PR and between 97bc73b and 3cf1fd9.

📒 Files selected for processing (4)
  • src/actions/my-usage.ts
  • src/hooks/use-ip-geo.ts
  • tests/api/my-usage-readonly.test.ts
  • tests/unit/actions/my-usage-ip-geo.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/use-ip-geo.ts

Comment thread src/actions/my-usage.ts Outdated
@ding113
ding113 merged commit c2b9b52 into dev Apr 19, 2026
8 of 9 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Apr 19, 2026

@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.

🧹 Nitpick comments (1)
tests/unit/actions/my-usage-ip-geo.test.ts (1)

109-125: 冗余的 mock 设置与断言自相矛盾

第 114 行设置了 dbLimit.mockResolvedValueOnce([{ id: 42 }]),但第 123 行又断言 dbLimit 未被调用。根据实现(getSystemSettings 检查先于 DB 查询),该 mock 不会被消费,会残留到下一个测试用例的队列中,虽然 beforeEach 中的 vi.clearAllMocks() 会清理,但阅读时仍造成混淆。建议移除该行以使测试意图更清晰。

建议的清理
     mocks.getSession.mockResolvedValueOnce({
       user: { id: 1 },
       key: { id: 10, key: "sk-test" },
     });
-    mocks.dbLimit.mockResolvedValueOnce([{ id: 42 }]);
     mocks.getSystemSettings.mockResolvedValueOnce({
       ipGeoLookupEnabled: false,
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/actions/my-usage-ip-geo.test.ts` around lines 109 - 125, The test
for getMyIpGeoDetails sets a redundant mocks.dbLimit.mockResolvedValueOnce([{
id: 42 }]) while also asserting mocks.dbLimit was not called; remove the
mocks.dbLimit.mockResolvedValueOnce(...) setup from the "系统关闭 IP 查询时返回禁用错误" test
so the mock queue isn't polluted and the expectation that dbLimit is not called
remains consistent with the getSystemSettings mock (ipGeoLookupEnabled: false);
keep the rest of the test (mocks.getSession, mocks.getSystemSettings, import of
getMyIpGeoDetails, and the assertions) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/unit/actions/my-usage-ip-geo.test.ts`:
- Around line 109-125: The test for getMyIpGeoDetails sets a redundant
mocks.dbLimit.mockResolvedValueOnce([{ id: 42 }]) while also asserting
mocks.dbLimit was not called; remove the
mocks.dbLimit.mockResolvedValueOnce(...) setup from the "系统关闭 IP 查询时返回禁用错误" test
so the mock queue isn't polluted and the expectation that dbLimit is not called
remains consistent with the getSystemSettings mock (ipGeoLookupEnabled: false);
keep the rest of the test (mocks.getSession, mocks.getSystemSettings, import of
getMyIpGeoDetails, and the assertions) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 12b39d05-bfca-41e5-8a2b-e27ee66ae907

📥 Commits

Reviewing files that changed from the base of the PR and between 3cf1fd9 and c3dc50e.

📒 Files selected for processing (2)
  • src/actions/my-usage.ts
  • tests/unit/actions/my-usage-ip-geo.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/actions/my-usage.ts

@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c3dc50e296

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/actions/my-usage.ts
.where(
and(
isNull(messageRequest.deletedAt),
EXCLUDE_WARMUP_CONDITION,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match warmup visibility when authorizing my-usage IP lookups

After this commit’s new getMyIpGeoDetails gate was added, the message_request scope check still hard-filters with EXCLUDE_WARMUP_CONDITION, but the my-usage table that opens this dialog (getMyUsageLogsBatchFullfindUsageLogsBatch) does not apply that warmup exclusion on message_request rows. In practice, a warmup log row can be visible in my-usage while the IP details action rejects it as “not found,” so users get a broken dialog for visible rows.

Useful? React with 👍 / 👎.

Comment thread src/actions/my-usage.ts
Comment on lines +739 to +740
.where(and(eq(usageLedger.key, session.key.key), eq(usageLedger.clientIp, ip)))
.limit(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Apply ledger billing filter in IP scope fallback

The new ledger-only fallback authorizes by usage_ledger.key + usage_ledger.clientIp only, but visible my-usage ledger logs are filtered with LEDGER_BILLING_CONDITION (blocked_by IS NULL) in findUsageLogsBatch. This means blocked ledger rows that are hidden from the table can still satisfy authorization here, allowing IP lookups for data outside the visible my-usage log scope.

Useful? React with 👍 / 👎.

@github-actions github-actions Bot mentioned this pull request Apr 19, 2026
5 tasks
@ding113
ding113 deleted the fix/my-usage-ip-geo-auth branch May 13, 2026 06:22
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 size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant