Skip to content

fix: enforce header nav access control for public modules#4889

Merged
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
yyhhyyyyyy:fix/header-nav-public-access-control
May 16, 2026
Merged

fix: enforce header nav access control for public modules#4889
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
yyhhyyyyyy:fix/header-nav-public-access-control

Conversation

@yyhhyyyyyy
Copy link
Copy Markdown
Contributor

@yyhhyyyyyy yyhhyyyyyy commented May 15, 2026

⚠️ 提交说明 / PR Notice

Important

  • 请提供人工撰写的简洁摘要,避免直接粘贴未经整理的 AI 输出。

📝 变更描述 / Description

(简述:做了什么?为什么这样改能生效?请基于你对代码逻辑的理解来写,避免粘贴未经整理的内容)
修复 HeaderNavModules 只影响导航展示、不保护实际访问的问题。
将模型广场和排行榜的访问控制下沉到后端中间件,并同步补齐前端路由守卫和公共 Header 交互。现在 enabled=false 会阻止公开接口访问,requireAuth=true 会要求用户登录。
前端未登录点击受限入口时会弹出登录提示并带 redirect 跳转登录,直连页面和接口也都有后端兜底保护。

🚀 变更类型 / Type of change

  • 🐛 Bug 修复 (Bug fix) - 请关联对应 Issue,避免将设计取舍、理解偏差或预期不一致直接归类为 bug
  • ✨ 新功能 (New feature) - 重大特性建议先通过 Issue 沟通
  • ⚡ 性能优化 / 重构 (Refactor)
  • 📝 文档更新 (Documentation)

🔗 关联任务 / Related Issue

✅ 提交前检查项 / Checklist

  • 人工确认: 我已亲自整理并撰写此描述,没有直接粘贴未经处理的 AI 输出。
  • 非重复提交: 我已搜索现有的 IssuesPRs,确认不是重复提交。
  • Bug fix 说明: 若此 PR 标记为 Bug fix,我已提交或关联对应 Issue,且不会将设计取舍、预期不一致或理解偏差直接归类为 bug。
  • 变更理解: 我已理解这些更改的工作原理及可能影响。
  • 范围聚焦: 本 PR 未包含任何与当前任务无关的代码改动。
  • 本地验证: 已在本地运行并通过测试或手动验证,维护者可以据此复核结果。
  • 安全合规: 代码中无敏感凭据,且符合项目代码规范。

📸 运行证明 / Proof of Work

(请在此粘贴截图、关键日志或测试报告,以证明变更生效)
image
image

Summary by CodeRabbit

  • New Features

    • Modules (Pricing, Rankings) can now require user authentication
    • Sign-in prompt dialog with countdown redirect for protected features
    • Navigation links dynamically indicate auth requirements
  • Refactor

    • Access control system for header navigation modules with flexible enable/require-auth configuration
    • Route guards for protected pages redirect unauthenticated users to sign-in
    • Navigation link handling updated to support auth-required state
  • Tests

    • Added comprehensive test coverage for authentication middleware and module access control
  • Documentation

    • Added multilingual sign-in prompts and redirect messages (English, French, Japanese, Russian, Vietnamese, Chinese)

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 15, 2026

Walkthrough

This PR implements module-level authentication gating for pricing and rankings features. The backend introduces configurable middleware for access control, route wiring applies it to API endpoints, and the frontend adds route guards, UI prompts, and updated navigation logic to enforce and communicate access restrictions.

Changes

Module Authentication Gating

Layer / File(s) Summary
Backend middleware for access control
middleware/header_nav.go, middleware/header_nav_test.go
New middleware reads HeaderNavModules from shared options, parses per-module enabled/requireAuth settings with fallback logic across multiple input types, and provides HeaderNavModuleAuth (blocks with 403 when disabled) and HeaderNavModulePublicOrUserAuth (allows public access only when enabled and not requiring auth) handlers.
Route wiring and controller cleanup
router/api-router.go, controller/rankings.go
API routes now apply module-specific middleware (HeaderNavModuleAuth for pricing/rankings, HeaderNavModulePublicOrUserAuth for perf-metrics). The rankings controller removes the old feature-flag check, delegating access control to middleware.
Frontend access config and types
web/default/src/lib/nav-modules.ts, web/default/src/components/layout/types.ts
New exported types (HeaderNavModule, HeaderNavModules, ModuleAccess) and parsing helpers (parseHeaderNavModulesFromStatus, getModuleAccessFromStatus) compute and cache module access from server status. TopNavLink gains an optional requiresAuth field.
Hook and link generation
web/default/src/hooks/use-top-nav-links.ts
useTopNavLinks delegates module parsing to shared helpers and sets requiresAuth (instead of disabled) for pricing/rankings links when modules require authentication and the user is not signed in.
Route guards with fresh access checks
web/default/src/routes/pricing/index.tsx, web/default/src/routes/pricing/$modelId/index.tsx, web/default/src/routes/rankings/index.tsx
Three routes now have async beforeLoad guards that fetch fresh module access and redirect: disabled modules redirect to /, auth-required modules redirect to /sign-in while preserving the original URL in search.redirect.
Auth-required prompt modal and interaction
web/default/src/components/layout/components/public-header.tsx
PublicHeader adds an auth-required dialog that opens when users click links marked requiresAuth, displays a countdown, and auto-redirects to sign-in; manual sign-in and cancel actions are also supported. Disabled and auth-required links receive proper accessibility attributes and centralized click handling through handleNavLinkClick.
Multilingual sign-in messaging
web/default/src/i18n/locales/*.json
New i18n keys added across 6 languages for module access gating: module sign-in prompts, redirect countdown, and sign-in CTAs/status labels.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The PR spans backend middleware, route wiring, frontend type/hook changes, route guards, UI interactions, and i18n updates across multiple files. The middleware parsing logic, frontend modal/countdown flow, and route guard implementations require careful verification of correctness and edge cases, particularly around fallback behavior and state management in the auth prompt modal.

Possibly related PRs

  • QuantumNous/new-api#4802: Prior PR that added 403 "rankings is disabled" check in controller/rankings.go; this PR removes that check in favor of middleware-based gating.
  • QuantumNous/new-api#1701: Added HeaderNavModules to the GetStatus payload from common.OptionMap, which is the configuration this PR's middleware and React code parse and enforce.

Suggested reviewers

  • Calcium-Ion
  • creamlike1024

Poem

A rabbit hops through the gates today, 🐰
Where pricing and rankings now guard their way,
With countdowns and modals that gently say,
"Sign in to see more!"—in every display.
In six tongues the message rings out so clear,
Access control blooms when users draw near. 🌸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: enforcing header nav access control for public modules, which is the core objective of this PR.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #4809 by implementing backend middleware to enforce HeaderNavModules access control, frontend route guards to protect direct access, and UI prompts for auth-required modules.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing header nav access control: backend middleware/routes, frontend route guards, UI interactions, and i18n translations for the new auth flow.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (5)
web/default/src/i18n/locales/en.json (1)

2935-2935: 🏗️ Heavy lift

Use hierarchical i18n keys for the new auth-gating strings.

These new phrase-based keys should be converted to a semantic namespace (e.g., auth.moduleAccess.signInPrompt, auth.moduleAccess.redirectCountdown, auth.moduleAccess.signInNow, auth.moduleAccess.signInRequired) to keep key naming consistent and maintainable across locales.

As per coding guidelines, web/default/src/i18n/**/*.{ts,tsx,json} should “Use hierarchical and semantically clear translation key names such as dashboard.overview.title and maintain naming consistency”.

Also applies to: 3171-3171, 3603-3604

🤖 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 `@web/default/src/i18n/locales/en.json` at line 2935, Replace the flat phrase
key "Please sign in to view {{module}}." with a semantic, hierarchical i18n key
(e.g., auth.moduleAccess.signInPrompt) and move the phrase into that new key in
en.json; likewise convert the related flat keys at the other referenced
locations (lines 3171 and 3603-3604) to the corresponding names
(auth.moduleAccess.redirectCountdown, auth.moduleAccess.signInNow,
auth.moduleAccess.signInRequired) so all auth-gating strings follow the
auth.moduleAccess namespace and maintain consistent naming across locales and
code.
web/default/src/i18n/locales/ru.json (1)

2935-2935: 🏗️ Heavy lift

Use hierarchical i18n keys for new auth prompts

These new entries use sentence literals as keys. Please migrate them to semantic hierarchical keys (for example, auth.signInRequired.message, auth.signInRequired.redirect, auth.signInRequired.cta, auth.signInRequired.title) and keep the same key names across locales.

As per coding guidelines, "Use hierarchical and semantically clear translation key names such as dashboard.overview.title and maintain naming consistency".

Also applies to: 3171-3171, 3603-3604

🤖 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 `@web/default/src/i18n/locales/ru.json` at line 2935, Replace the
sentence-literal i18n key "Please sign in to view {{module}}." with a
hierarchical semantic key like auth.signInRequired.message (and add related keys
if needed: auth.signInRequired.title, auth.signInRequired.cta,
auth.signInRequired.redirect), update the ru.json entry to use
auth.signInRequired.message: "Войдите, чтобы просмотреть {{module}}.", and apply
identical key names in all other locale files mentioned (the other literal
entries flagged) so keys are consistent across locales; also update any code
references that currently use the sentence literal to use
auth.signInRequired.message (or the specific new key variant) to avoid breaking
lookups.
web/default/src/lib/nav-modules.ts (1)

25-33: ⚡ Quick win

Narrow the exported contract or parse access objects generically.

HeaderNavModules says any key may be a ModuleAccess, but parseHeaderNavModules() only supports object-shaped access config for pricing and rankings. If another module is later added as { enabled, requireAuth }, this parser will silently drop that object path here. Either narrow the index signature to the currently supported keys or route unknown object-valued entries through parseAccess(...) too.

Also applies to: 107-137

🤖 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 `@web/default/src/lib/nav-modules.ts` around lines 25 - 33, HeaderNavModules
currently allows any key to be ModuleAccess but parseHeaderNavModules only
parses object-shaped access for pricing and rankings, risking silent drops for
other object-valued keys; either restrict HeaderNavModules index signature to
only the known keys (pricing, rankings) or update parseHeaderNavModules to call
parseAccess(...) for any entry whose value is an object so unknown modules with
{enabled, requireAuth} are handled generically; update the type and/or the
parsing loop in parseHeaderNavModules accordingly and ensure parseAccess is
imported/used for all object-valued entries.
web/default/src/routes/pricing/index.tsx (1)

40-54: ⚡ Quick win

Extract this guard before the access policy drifts.

This beforeLoad block is now effectively copied across /pricing/, /pricing/$modelId/, and /rankings/. Any future change to redirect behavior or auth checks has to land in three places. A small helper that takes the module name would keep these route guards consistent.

As per coding guidelines, keep function cyclomatic complexity reasonable; break complex logic into smaller functions with meaningful variable and function names following camelCase conventions.

🤖 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 `@web/default/src/routes/pricing/index.tsx` around lines 40 - 54, Extract the
repeated guard into a reusable helper: create a function (e.g.,
enforceModuleAccess or ensureModuleAccess) that accepts the module name and
location, calls getFreshModuleAccess(moduleName), throws redirect({ to: '/' })
if access.enabled is false, and checks access.requireAuth by reading
useAuthStore.getState().auth.user and redirecting to '/sign-in' with search: {
redirect: location.href } when missing; then replace the inline beforeLoad logic
in beforeLoad handlers with a single call to this helper to keep redirect/auth
behavior consistent across routes.
web/default/src/hooks/use-top-nav-links.ts (1)

25-31: ⚡ Quick win

Reuse the shared TopNavLink type here.

This hook now owns a second TopNavLink definition that has to stay in lockstep with web/default/src/components/layout/types.ts. This PR already had to touch both copies for requiresAuth, so importing the shared type will avoid the next drift.

♻️ Proposed cleanup
 import { useTranslation } from 'react-i18next'
 import { useAuthStore } from '@/stores/auth-store'
 import { useStatus } from '@/hooks/use-status'
 import { parseHeaderNavModulesFromStatus } from '@/lib/nav-modules'
+import type { TopNavLink } from '@/components/layout/types'
-
-export type TopNavLink = {
-  title: string
-  href: string
-  disabled?: boolean
-  requiresAuth?: boolean
-  external?: boolean
-}

As per coding guidelines, use import type { X } from '...' for type-only imports in TypeScript.

🤖 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 `@web/default/src/hooks/use-top-nav-links.ts` around lines 25 - 31, Replace the
local TopNavLink type in use-top-nav-links.ts with a type-only import of the
shared TopNavLink definition: remove the duplicated export type TopNavLink =
{...} and add import type { TopNavLink } from 'components/layout/types' (or the
correct relative module path) so the hook reuses the centralized type and you
avoid drift; use an import type statement per TS guidelines.
🤖 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.

Nitpick comments:
In `@web/default/src/hooks/use-top-nav-links.ts`:
- Around line 25-31: Replace the local TopNavLink type in use-top-nav-links.ts
with a type-only import of the shared TopNavLink definition: remove the
duplicated export type TopNavLink = {...} and add import type { TopNavLink }
from 'components/layout/types' (or the correct relative module path) so the hook
reuses the centralized type and you avoid drift; use an import type statement
per TS guidelines.

In `@web/default/src/i18n/locales/en.json`:
- Line 2935: Replace the flat phrase key "Please sign in to view {{module}}."
with a semantic, hierarchical i18n key (e.g., auth.moduleAccess.signInPrompt)
and move the phrase into that new key in en.json; likewise convert the related
flat keys at the other referenced locations (lines 3171 and 3603-3604) to the
corresponding names (auth.moduleAccess.redirectCountdown,
auth.moduleAccess.signInNow, auth.moduleAccess.signInRequired) so all
auth-gating strings follow the auth.moduleAccess namespace and maintain
consistent naming across locales and code.

In `@web/default/src/i18n/locales/ru.json`:
- Line 2935: Replace the sentence-literal i18n key "Please sign in to view
{{module}}." with a hierarchical semantic key like auth.signInRequired.message
(and add related keys if needed: auth.signInRequired.title,
auth.signInRequired.cta, auth.signInRequired.redirect), update the ru.json entry
to use auth.signInRequired.message: "Войдите, чтобы просмотреть {{module}}.",
and apply identical key names in all other locale files mentioned (the other
literal entries flagged) so keys are consistent across locales; also update any
code references that currently use the sentence literal to use
auth.signInRequired.message (or the specific new key variant) to avoid breaking
lookups.

In `@web/default/src/lib/nav-modules.ts`:
- Around line 25-33: HeaderNavModules currently allows any key to be
ModuleAccess but parseHeaderNavModules only parses object-shaped access for
pricing and rankings, risking silent drops for other object-valued keys; either
restrict HeaderNavModules index signature to only the known keys (pricing,
rankings) or update parseHeaderNavModules to call parseAccess(...) for any entry
whose value is an object so unknown modules with {enabled, requireAuth} are
handled generically; update the type and/or the parsing loop in
parseHeaderNavModules accordingly and ensure parseAccess is imported/used for
all object-valued entries.

In `@web/default/src/routes/pricing/index.tsx`:
- Around line 40-54: Extract the repeated guard into a reusable helper: create a
function (e.g., enforceModuleAccess or ensureModuleAccess) that accepts the
module name and location, calls getFreshModuleAccess(moduleName), throws
redirect({ to: '/' }) if access.enabled is false, and checks access.requireAuth
by reading useAuthStore.getState().auth.user and redirecting to '/sign-in' with
search: { redirect: location.href } when missing; then replace the inline
beforeLoad logic in beforeLoad handlers with a single call to this helper to
keep redirect/auth behavior consistent across routes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ac7731d6-efd7-4bbb-b67b-60d6a5a3f8a2

📥 Commits

Reviewing files that changed from the base of the PR and between 18282e6 and 1b0d608.

📒 Files selected for processing (17)
  • controller/rankings.go
  • middleware/header_nav.go
  • middleware/header_nav_test.go
  • router/api-router.go
  • web/default/src/components/layout/components/public-header.tsx
  • web/default/src/components/layout/types.ts
  • web/default/src/hooks/use-top-nav-links.ts
  • web/default/src/i18n/locales/en.json
  • web/default/src/i18n/locales/fr.json
  • web/default/src/i18n/locales/ja.json
  • web/default/src/i18n/locales/ru.json
  • web/default/src/i18n/locales/vi.json
  • web/default/src/i18n/locales/zh.json
  • web/default/src/lib/nav-modules.ts
  • web/default/src/routes/pricing/$modelId/index.tsx
  • web/default/src/routes/pricing/index.tsx
  • web/default/src/routes/rankings/index.tsx
💤 Files with no reviewable changes (1)
  • controller/rankings.go

Copy link
Copy Markdown
Collaborator

@Calcium-Ion Calcium-Ion left a comment

Choose a reason for hiding this comment

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

LGTM

@Calcium-Ion Calcium-Ion merged commit 6f8668e into QuantumNous:main May 16, 2026
2 checks passed
chenglu added a commit to chenglu/new-api that referenced this pull request May 17, 2026
Merged 28 commits from QuantumNous/new-api upstream/main, including:

Backend:
- feat: support request_header key source (QuantumNous#4903)
- fix: apply group filter to channel list queries (QuantumNous#4885, QuantumNous#4847)
- fix: enforce header nav access control for public modules (QuantumNous#4889)
- fix: correct usage logs filtering (QuantumNous#4883)
- fix: allow clearing channel remark (QuantumNous#4886)
- feat: track upstream request ID and prevent response header override
- feat: require compliance confirmation for paid features

Frontend:
- fix: 修复新 UI 语言与文案显示问题 (QuantumNous#4876)
- fix(web): handle unlimited API key quota validation (QuantumNous#4881)
- fix(web/default): batch fix new UI issues (QuantumNous#4880, QuantumNous#4893, QuantumNous#4817, QuantumNous#4877, QuantumNous#4898)
- fix: prevent combobox from over-filtering options on focus (QuantumNous#4829)
- fix(default): support DropdownMenuItem onSelect (QuantumNous#4787)
- chore(deps): bump axios to 1.15.2

Conflict resolution:
- Locale JSONs: union merge (design overrides preserved, upstream new keys added)
- router/api-router.go: kept design /public/session route, accepted upstream HeaderNavModuleAuth
- common-logs-filter-bar.tsx: kept design refactor, added upstream upstreamRequestId field
- summary-cards.tsx: kept design layout, adapted to new useSummaryCardsConfig interface
- _reports/*.untranslated.json: design state kept (fr/vi removed, ja/ru ours)
- Other UI conflicts resolved per .gitattributes ours strategy

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants