Skip to content

feat(bines): 固定已有身份并提供管理员接入管理 - #12

Open
yororoA wants to merge 2 commits into
perf/initial-loadfrom
feat/bines-mcp
Open

yororoA wants to merge 2 commits into
perf/initial-loadfrom
feat/bines-mcp

Conversation

@yororoA

@yororoA yororoA commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

新增 /bines 公开资料和在线状态页,只展示 arkserver 的 BINES_UID 指定的已有账号;移除搜索、创建和更换账号入口。只有经过验证的管理员可编辑资料、签发/撤销凭证和查看近期活动。复制配置包含 ARK_MCP_UID,完整 token 仅保存在当前弹窗内,关闭后清除。

复用现有组件,支持档案、青笺、P3R 三主题及中英日德。管理区按需加载,BFF 验证管理员与同源请求;签名 webhook 更新内容缓存。隔离预览每次清理旧缓存,避免上轮测试数据污染。

验证:21/21 集成组、TypeScript、生产构建与资源预算通过;lint 0 error,2 个既有 warning;访客/普通用户/Bines 只读、管理员编辑和配置、三主题 1440/390/320px 浏览器回归通过。

本 PR 基于 perf/initial-load,保留尚未合并的首屏优化;该分支合并后可改 base 到 main。后端依赖 https://github.com/yororoA/yororoIceBlogBackend/pull/5 。未部署。

Summary by Sourcery

Add a fixed-identity Bines profile and secure administrator access-management workflow with signed content revalidation.

New Features:

  • Add a public /bines profile and presence page for the configured existing Bines account, with links from the homepage, footer, and sitemap.
  • Provide verified administrators with profile editing, scoped credential issuance and revocation, credential activity viewing, and one-time configuration copying.
  • Support signed backend content-update webhooks that invalidate shared blog caches.

Bug Fixes:

  • Prevent unauthorized, cross-origin, forged-session, unlisted-route, and client-supplied identity requests from reaching Bines administration endpoints.
  • Ensure isolated preview verification starts with a clean Next.js cache to avoid stale fixture data.

Enhancements:

  • Localize the Bines experience across Chinese, English, Japanese, and German while integrating the existing themes and responsive layouts.
  • Keep administrative data lazy-loaded and prevent plaintext access tokens from being persisted beyond the credential dialog.

Documentation:

  • Document Bines identity configuration, administrator access management, credential setup, webhook configuration, and validation procedures.

Tests:

  • Extend blog integration verification to cover Bines visibility, authorization boundaries, credential lifecycle, signed webhook validation, cache refresh, and isolated preview behavior.

Chores:

  • Exclude local integration worktrees from linting.

@sourcery-ai

sourcery-ai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

本 PR 将 Bines 从可管理的账号入口改造成绑定后端既有身份的公开资料与在线状态页,并通过受保护的 BFF 管理资料、凭证和活动;同时加入一次性 token 配置交付、签名缓存失效 webhook、多主题多语言 UI,以及覆盖安全边界和隔离预览的集成验证。

Sequence diagram for protected Bines administration

sequenceDiagram
    actor Admin
    participant UI as Bines admin UI
    participant BFF as Bines BFF API
    participant Backend as arkserver backend

    Admin->>UI: Open /bines
    UI->>BFF: GET /api/bines/admin
    BFF->>BFF: verifiedSession()
    BFF->>BFF: sameOrigin()
    BFF->>Backend: GET /api/bines/admin
    Backend-->>BFF: Profile credentials activity
    BFF-->>UI: Admin data
    Admin->>UI: Edit profile or issue credential
    UI->>BFF: PUT /api/bines/admin/profile or POST /api/bines/admin/credentials
    BFF->>Backend: Forward allowlisted request
    Backend-->>BFF: Updated profile or token
    BFF-->>UI: Result
    UI->>UI: Keep full token only in mounted dialog
Loading

Sequence diagram for signed Bines content revalidation

sequenceDiagram
    participant Backend as arkserver backend
    participant Webhook as /api/bines/revalidate
    participant Cache as Next public-blog-content cache
    participant Page as Public content page

    Backend->>Backend: Persist moments or comments
    Backend->>Webhook: POST signed event
    Webhook->>Webhook: Validate timestamp and HMAC-SHA256
    Webhook->>Webhook: Validate kind=moments
    Webhook->>Cache: revalidateTag(public-blog-content)
    Cache-->>Page: Fetch fresh content on next request
    Webhook-->>Backend: Revalidated
Loading

File-Level Changes

Change Details Files
新增固定 Bines 身份的公开资料页、在线状态展示及多语言主题化管理界面。
  • 通过后端 BINES_UID 绑定已有账号,移除账号搜索、创建和切换能力
  • 新增公开资料、状态、动态/聊天入口,并接入首页、页脚和 sitemap
  • 复用现有 UI 组件,支持三种主题和中英日德本地化
  • 管理员区按需加载,支持资料编辑、近期活动查看和凭证生命周期管理
src/app/(blog)/bines/page.tsx
src/components/blog/bines.tsx
src/components/blog/bines-admin.tsx
src/components/blog/bines.module.scss
src/lib/bines.ts
src/lib/blog.ts
src/components/blog/blog-shell.tsx
src/components/blog/home.tsx
src/app/sitemap.ts
实现 Bines 管理 API 的安全代理与一次性凭证配置交付。
  • 仅允许明确的 profile/admin 路径,并对管理请求执行同源、验证会话和管理员权限检查
  • 不信任浏览器传入的 Authorization、UID 或权限字段,统一使用服务端会话凭证访问后端
  • 对请求体大小、JSON 格式、响应缓存和凭证列表脱敏进行限制
  • 完整 token 仅保存在当前签发弹窗,关闭或切换账号后不持久化
src/app/api/bines/[...path]/route.ts
src/components/blog/bines-admin.tsx
src/env.d.ts
docs/bines-integration.md
增加带 HMAC 校验的内容缓存失效 webhook,并覆盖后端更新通知流程。
  • 校验时间窗口、签名格式、原始请求体和事件类型后失效共享公开内容缓存标签
  • 拒绝伪造、过期、篡改及任意缓存标签请求,同时允许重复通知安全处理
  • 补充后端配置、部署顺序和缓存行为说明
src/app/api/bines/revalidate/route.ts
docs/bines-integration.md
扩展集成验证,覆盖 Bines 权限边界、凭证操作、webhook 和隔离预览缓存。
  • 验证访客、普通用户、Bines 账号、伪造会话及跨站请求均不能执行管理操作
  • 验证资料编辑、签发/脱敏/撤销凭证和签名 webhook 的完整生命周期
  • 每次隔离预览清理旧 .next 缓存,避免测试数据污染,并忽略本地工作目录
scripts/verify-blog.mjs
eslint.config.mjs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@vercel

vercel Bot commented Oct 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ark Ready Ready Preview Oct 13, 2026 9:43am UTC

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

Hey - I've found 5 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/app/api/bines/revalidate/route.ts" line_range="17-18" />
<code_context>
+    const headers = new Headers(auth ? credentialHeaders(auth.credential) : {})
+    let body: string | undefined
+    if (request.method === 'POST' || request.method === 'PUT') {
+      const raw = await request.text()
+      if (Buffer.byteLength(raw) > 8192) return fail('请求内容过长', 413)
+      body = JSON.stringify(JSON.parse(raw))
</code_context>
<issue_to_address>
**Oversized webhooks exhaust memory**

When an unauthenticated caller sends oversized request bodies to the public webhook, `request.text()` buffers the entire body before the 1 KB check, so concurrent oversized requests consume memory before returning 413 and can take the site offline.

Enforce the byte limit while reading the request stream, rejecting oversized bodies before buffering them in full.

Also at `src/app/api/bines/[...path]/route.ts:25-27`.
</issue_to_address>

### Comment 2
<location path="src/app/api/bines/[...path]/route.ts" line_range="19-32" />
<code_context>
+    const auth = isPublic ? null : await verifiedSession()
</code_context>
<issue_to_address>
**Slow backend requests time out**

When an admin request runs on a host whose function limit is shorter than its sequential upstream calls and the backend is slow, the route awaits two session-verification calls and then another upstream call, each with a 30-second timeout. On hosts with a shorter function execution limit, the platform terminates a slow request before these timeouts or the route's error handling complete, and the caller receives a gateway timeout.

Set an appropriate route execution limit for supported hosts or use a shorter aggregate deadline for the whole request.
</issue_to_address>

### Comment 3
<location path="src/components/blog/bines-admin.tsx" line_range="31-32" />
<code_context>
+    setBusy(true); setError('')
+    try {
+      await send('/api/bines/admin/profile', {
+        displayName: name.trim(), bio: bio.trim(),
+      }, 'PUT')
+      await refresh(); notify(t('saved')); close()
+    } catch { setError(text('requestFailed')) } finally { setBusy(false) }
</code_context>
<issue_to_address>
**Concurrent profile edits overwrite each other**

When two administrators edit the profile concurrently and save changes to different fields, admin A can save a display-name change, then Admin B can save a bio change from a form opened earlier. Because each PUT sends both fields from its original snapshot, B restores A’s old display name and A’s change disappears.

Use version-based conflict detection or update only fields changed against the latest profile.
</issue_to_address>

### Comment 4
<location path="src/app/api/bines/[...path]/route.ts" line_range="19" />
<code_context>
+    if (!routes[request.method]?.test(path)) return fail('接口不存在', 404)
+    const isPublic = request.method === 'GET' && path === 'profile'
+    if (!isPublic && !sameOrigin(request)) return fail('请求来源无效', 403)
+    const auth = isPublic ? null : await verifiedSession()
+    if (!isPublic && !auth) return fail('请先登录', 401)
+    if (!isPublic && !auth?.session.isAdmin) return fail('需要管理员登录', 403)
</code_context>
<issue_to_address>
**Backend outage appears as logout**

When the backend `/api/v2/auth/me` endpoint returns a 5xx response, `verifiedSession()` returns `null` when `/api/v2/auth/me` responds with any non-OK status, including 5xx. This route then returns 401, so an administrator with a valid session is told to log in instead of seeing a service error.

Treat upstream 5xx responses as backend errors and return a service-unavailable response rather than an unauthenticated result.
</issue_to_address>

### Comment 5
<location path="src/components/blog/home.tsx" line_range="151-153" />
<code_context>
       </div>
       <div className={styles['hero-bottom']}>
-        <span className={styles['status']} data-online={isOnline}>
+        <Link href="/bines" className={styles['status']} data-online={isOnline}>
           <i />BINES · {t(statusLabel)}
-        </span>
+        </Link>
         {/* The former Latin ornament looked like an advertising label. */}
         <a className={styles['scroll-link']} href="#recent">
</code_context>
<issue_to_address>
**P3R status cannot open Bines**

When a visitor uses the P3R theme, `P3RHome` renders the Bines status as a `<span>`, so visitors cannot follow the homepage status to `/bines`.

Render the P3R Bines status as a link to `/bines`, matching the standard hero.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 5 findings to address first, and this adds an admin path that can issue bearer credentials with scopes for reading data, publishing, interactions, or chat. If a credential is issued too broadly or to the wrong party, access or messages can outlive a revert; reverting the frontend does not revoke credentials already issued.

Blocking findings: src/app/api/bines/revalidate/route.ts:18, src/app/api/bines/[...path]/route.ts:32, src/components/blog/bines-admin.tsx:32, src/app/api/bines/[...path]/route.ts:19, src/components/blog/home.tsx:153


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +17 to +18
const raw = await request.text()
if (Buffer.byteLength(raw) > 1024) return reply(413, 'Payload too large')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Oversized webhooks exhaust memory

When an unauthenticated caller sends oversized request bodies to the public webhook, request.text() buffers the entire body before the 1 KB check, so concurrent oversized requests consume memory before returning 413 and can take the site offline.

Enforce the byte limit while reading the request stream, rejecting oversized bodies before buffering them in full.

Also at src/app/api/bines/[...path]/route.ts:25-27.

Prompt for AI agents
In `src/app/api/bines/revalidate/route.ts` at lines 17-18:

**Oversized webhooks exhaust memory**

When an unauthenticated caller sends oversized request bodies to the public webhook, `request.text()` buffers the entire body before the 1 KB check, so concurrent oversized requests consume memory before returning 413 and can take the site offline.

Enforce the byte limit while reading the request stream, rejecting oversized bodies before buffering them in full.

Also at `src/app/api/bines/[...path]/route.ts:25-27`.

Comment on lines +19 to +32
const auth = isPublic ? null : await verifiedSession()
if (!isPublic && !auth) return fail('请先登录', 401)
if (!isPublic && !auth?.session.isAdmin) return fail('需要管理员登录', 403)
const headers = new Headers(auth ? credentialHeaders(auth.credential) : {})
let body: string | undefined
if (request.method === 'POST' || request.method === 'PUT') {
const raw = await request.text()
if (Buffer.byteLength(raw) > 8192) return fail('请求内容过长', 413)
body = JSON.stringify(JSON.parse(raw))
headers.set('Content-Type', 'application/json')
}
// Query and endpoint are explicitly allowlisted; incoming credentials and
// client-supplied administrator/sender headers are never forwarded.
const response = await upstream(`/api/bines/${path}`, { method: request.method, headers, body })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Slow backend requests time out

When an admin request runs on a host whose function limit is shorter than its sequential upstream calls and the backend is slow, the route awaits two session-verification calls and then another upstream call, each with a 30-second timeout. On hosts with a shorter function execution limit, the platform terminates a slow request before these timeouts or the route's error handling complete, and the caller receives a gateway timeout.

Set an appropriate route execution limit for supported hosts or use a shorter aggregate deadline for the whole request.

Prompt for AI agents
In `src/app/api/bines/[...path]/route.ts` at lines 19-32:

**Slow backend requests time out**

When an admin request runs on a host whose function limit is shorter than its sequential upstream calls and the backend is slow, the route awaits two session-verification calls and then another upstream call, each with a 30-second timeout. On hosts with a shorter function execution limit, the platform terminates a slow request before these timeouts or the route's error handling complete, and the caller receives a gateway timeout.

Set an appropriate route execution limit for supported hosts or use a shorter aggregate deadline for the whole request.

Comment on lines +31 to +32
displayName: name.trim(), bio: bio.trim(),
}, 'PUT')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Concurrent profile edits overwrite each other

When two administrators edit the profile concurrently and save changes to different fields, admin A can save a display-name change, then Admin B can save a bio change from a form opened earlier. Because each PUT sends both fields from its original snapshot, B restores A’s old display name and A’s change disappears.

Use version-based conflict detection or update only fields changed against the latest profile.

Prompt for AI agents
In `src/components/blog/bines-admin.tsx` at lines 31-32:

**Concurrent profile edits overwrite each other**

When two administrators edit the profile concurrently and save changes to different fields, admin A can save a display-name change, then Admin B can save a bio change from a form opened earlier. Because each PUT sends both fields from its original snapshot, B restores A’s old display name and A’s change disappears.

Use version-based conflict detection or update only fields changed against the latest profile.

if (!routes[request.method]?.test(path)) return fail('接口不存在', 404)
const isPublic = request.method === 'GET' && path === 'profile'
if (!isPublic && !sameOrigin(request)) return fail('请求来源无效', 403)
const auth = isPublic ? null : await verifiedSession()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Backend outage appears as logout

When the backend /api/v2/auth/me endpoint returns a 5xx response, verifiedSession() returns null when /api/v2/auth/me responds with any non-OK status, including 5xx. This route then returns 401, so an administrator with a valid session is told to log in instead of seeing a service error.

Treat upstream 5xx responses as backend errors and return a service-unavailable response rather than an unauthenticated result.

Prompt for AI agents
In `src/app/api/bines/[...path]/route.ts` at line 19:

**Backend outage appears as logout**

When the backend `/api/v2/auth/me` endpoint returns a 5xx response, `verifiedSession()` returns `null` when `/api/v2/auth/me` responds with any non-OK status, including 5xx. This route then returns 401, so an administrator with a valid session is told to log in instead of seeing a service error.

Treat upstream 5xx responses as backend errors and return a service-unavailable response rather than an unauthenticated result.

Comment on lines +151 to +153
<Link href="/bines" className={styles['status']} data-online={isOnline}>
<i />BINES · {t(statusLabel)}
</span>
</Link>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · P3R status cannot open Bines

When a visitor uses the P3R theme, P3RHome renders the Bines status as a <span>, so visitors cannot follow the homepage status to /bines.

Render the P3R Bines status as a link to /bines, matching the standard hero.

Prompt for AI agents
In `src/components/blog/home.tsx` at lines 151-153:

**P3R status cannot open Bines**

When a visitor uses the P3R theme, `P3RHome` renders the Bines status as a `<span>`, so visitors cannot follow the homepage status to `/bines`.

Render the P3R Bines status as a link to `/bines`, matching the standard hero.

This branch was successfully deployed

1 active deployment
Preview — 4df28f00 Deployed Oct 13, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant