Repository navigation
Conversation
Reviewer's Guide本 PR 将 Bines 从可管理的账号入口改造成绑定后端既有身份的公开资料与在线状态页,并通过受保护的 BFF 管理资料、凭证和活动;同时加入一次性 token 配置交付、签名缓存失效 webhook、多主题多语言 UI,以及覆盖安全边界和隔离预览的集成验证。 Sequence diagram for protected Bines administrationsequenceDiagram
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
Sequence diagram for signed Bines content revalidationsequenceDiagram
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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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
| const raw = await request.text() | ||
| if (Buffer.byteLength(raw) > 1024) return reply(413, 'Payload too large') |
There was a problem hiding this comment.
🟠 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`.| 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 }) |
There was a problem hiding this comment.
🟡 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.| displayName: name.trim(), bio: bio.trim(), | ||
| }, 'PUT') |
There was a problem hiding this comment.
🟡 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() |
There was a problem hiding this comment.
🟡 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.| <Link href="/bines" className={styles['status']} data-online={isOnline}> | ||
| <i />BINES · {t(statusLabel)} | ||
| </span> | ||
| </Link> |
There was a problem hiding this comment.
🟡 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.
新增 /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:
/binesprofile and presence page for the configured existing Bines account, with links from the homepage, footer, and sitemap.Bug Fixes:
Enhancements:
Documentation:
Tests:
Chores: