[Follow-up] Add experimental WebUI for short messages with session-based login#942
[Follow-up] Add experimental WebUI for short messages with session-based login#942
Conversation
Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com>
(cherry picked from commit c7137ff)
Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com> (cherry picked from commit 07d7590) Update feature.yml Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com> (cherry picked from commit 1a99430) Update docs.yml Signed-off-by: Zhu Chenrui <boomzero_zcr@outlook.com> (cherry picked from commit 6017bcf)
Signed-off-by: Shan Wenxiao <seanoj_noreply@yeah.net>
…ns[bot] This prevents infinite loops where the bot commits version updates, which triggers the workflow again, causing another commit. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The last-commit-author guard now only exits for non-edited events, so PR title/body changes still update Update.json metadata even when the branch tip is a github-actions[bot] commit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exclude all bot actors (not just github-actions[bot]) from triggering the UpdateVersion workflow, preventing loops from AI code review bots. Allow edited events through the script-level guard so PR title/body changes still update Update.json metadata. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update to release 3.2.2
Reviewer's GuideAdds an experimental standalone web UI (webui.html) for XMOJ short messages with session-based authentication, wires it to existing API endpoints using the same RequestAPI payload shape as the userscript, and documents/links it from the homepage and README. Sequence diagram for WebUI login and session-based initializationsequenceDiagram
actor User
participant BrowserWebUI
participant XMOJLoginServer
participant SessionStorage
participant ShortMessageAPI
User->>BrowserWebUI: Open webuihtml
BrowserWebUI->>SessionStorage: loadSession
SessionStorage-->>BrowserWebUI: Stored username and SessionID (optional)
BrowserWebUI->>BrowserWebUI: Pre-fill username and SessionID
User->>BrowserWebUI: Click loginBtn (loginAndGetSession)
BrowserWebUI->>XMOJLoginServer: POST loginphp (user_id, md5password)
XMOJLoginServer-->>BrowserWebUI: HTML response + SetCookie PHPSESSID
BrowserWebUI->>BrowserWebUI: Extract PHPSESSID from response headers
alt SessionID extracted
BrowserWebUI->>BrowserWebUI: Update stateusername and statesessionId
BrowserWebUI->>SessionStorage: persistSession(username, SessionID)
BrowserWebUI->>ShortMessageAPI: POST GetMailList with RequestAPI payload
ShortMessageAPI-->>BrowserWebUI: MailList response
BrowserWebUI->>BrowserWebUI: Render conversation list
else SessionID not available
BrowserWebUI->>User: Show error hint (fill PHPSESSID manually)
end
User->>BrowserWebUI: Optionally click saveSessionBtn
BrowserWebUI->>BrowserWebUI: Read username and PHPSESSID inputs
BrowserWebUI->>BrowserWebUI: Update stateusername and statesessionId
BrowserWebUI->>SessionStorage: persistSession(username, SessionID)
BrowserWebUI->>ShortMessageAPI: POST GetMailList
ShortMessageAPI-->>BrowserWebUI: MailList response
BrowserWebUI->>BrowserWebUI: Render conversation list
Sequence diagram for sending a message with optional pasted imagesequenceDiagram
actor User
participant BrowserWebUI
participant SessionStorage
participant ShortMessageAPI
participant AssetServer
User->>BrowserWebUI: Select conversation in list
BrowserWebUI->>ShortMessageAPI: POST ReadUserMailMention
ShortMessageAPI-->>BrowserWebUI: Success
BrowserWebUI->>ShortMessageAPI: POST GetMail(UserID)
ShortMessageAPI-->>BrowserWebUI: Mail list
BrowserWebUI->>BrowserWebUI: Render message bubbles
User->>BrowserWebUI: Paste image into messageInput
BrowserWebUI->>BrowserWebUI: Detect image in clipboard items
BrowserWebUI->>ShortMessageAPI: POST UploadImage(Image DataURL)
ShortMessageAPI-->>BrowserWebUI: ImageID
BrowserWebUI->>AssetServer: Build image URL with ImageID
AssetServer-->>BrowserWebUI: Image URL
BrowserWebUI->>BrowserWebUI: Insert Markdown  into messageInput
User->>BrowserWebUI: Click sendBtn
BrowserWebUI->>SessionStorage: Read stored username and SessionID
BrowserWebUI->>ShortMessageAPI: POST SendMail(ToUser, Content)
ShortMessageAPI-->>BrowserWebUI: Success
BrowserWebUI->>BrowserWebUI: Clear messageInput and show success
BrowserWebUI->>ShortMessageAPI: POST GetMail(UserID)
ShortMessageAPI-->>BrowserWebUI: Updated mail list
BrowserWebUI->>BrowserWebUI: Re-render messages and conversation list
Class diagram for WebUI state, session, and API helpersclassDiagram
class State {
string username
string sessionId
string selectedUser
}
class Elements {
HTMLElement username
HTMLElement password
HTMLElement sessionId
HTMLElement loginBtn
HTMLElement saveSessionBtn
HTMLElement clearBtn
HTMLElement loginStatus
HTMLElement refreshListBtn
HTMLElement conversationList
HTMLElement refreshMessageBtn
HTMLElement currentTarget
HTMLElement messageList
HTMLElement messageInput
HTMLElement sendBtn
HTMLElement sendStatus
}
class SessionManager {
string STORAGE_KEY
persistSession()
loadSession()
clearAll()
}
class ApiClient {
string API_BASE
apiRequest(action, data)
}
class LoginController {
string LOGIN_URL
loginAndGetSession()
}
class MessageController {
refreshConversationList()
refreshMessages()
uploadImageFromPaste(file)
}
class UIHelpers {
setStatus(msg, isError)
setSendStatus(msg, isError)
formatTime(ts)
escapeHtml(text)
renderContent(content)
}
class EventBindings {
bindLoginButton()
bindSaveSessionButton()
bindClearButton()
bindRefreshListButton()
bindRefreshMessageButton()
bindSendButton()
bindPasteHandler()
}
State <.. SessionManager : manages
State <.. ApiClient : reads
State <.. LoginController : updates
State <.. MessageController : reads and updates
Elements <.. EventBindings : uses
Elements <.. UIHelpers : updates DOM
SessionManager --> State
ApiClient --> State
LoginController --> State
MessageController --> State
MessageController --> ApiClient
LoginController --> ApiClient
UIHelpers <.. MessageController
UIHelpers <.. LoginController
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | ||
| <title>XMOJ 短消息 WebUI(实验)</title> | ||
| <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet"> | ||
| <script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script> |
Check failure
Code scanning / CodeQL
Untrusted domain used in script or other content High
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The
loginAndGetSessionflow relies on readingSet-Cookiefrom the cross-originlogin.phpresponse, but browsers generally do not exposeSet-Cookieheaders to JS for security/CORS reasons, so this is likely to fail in practice; consider switching to a same-origin helper endpoint, a server-side proxy, or explicitly documenting that only manualPHPSESSIDinput is reliable. - There are a few hard-coded strings and config values repeated across the script (e.g.,
webui-0.1.0in both the payload and headers,API_BASE/endpoints), which could be centralized into a small config object to make future updates less error-prone.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `loginAndGetSession` flow relies on reading `Set-Cookie` from the cross-origin `login.php` response, but browsers generally do not expose `Set-Cookie` headers to JS for security/CORS reasons, so this is likely to fail in practice; consider switching to a same-origin helper endpoint, a server-side proxy, or explicitly documenting that only manual `PHPSESSID` input is reliable.
- There are a few hard-coded strings and config values repeated across the script (e.g., `webui-0.1.0` in both the payload and headers, `API_BASE`/endpoints), which could be centralized into a small config object to make future updates less error-prone.
## Individual Comments
### Comment 1
<location path="webui.html" line_range="307-314" />
<code_context>
+ body.set('user_id', username);
+ body.set('password', md5(password));
+
+ const response = await fetch(LOGIN_URL, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
+ body: body.toString(),
+ credentials: 'include'
+ });
+ const text = await response.text();
+ const setCookie = response.headers.get('set-cookie') || '';
+ let sessionId = '';
+
</code_context>
<issue_to_address>
**issue (bug_risk):** Reading `Set-Cookie` from `fetch` response headers in browser won’t work due to security restrictions.
In browsers, `response.headers.get('set-cookie')` is never available to JS (even with `credentials: 'include'`) because `Set-Cookie` is a forbidden CORS response header. As a result, `sessionId` will always be empty and this auto-extraction path won’t work in browser contexts. Consider either relying only on manual PHPSESSID input, routing this through your own backend that returns the session ID in a custom header/body, or clearly limiting/guarding this behavior to non-browser environments.
</issue_to_address>
### Comment 2
<location path="webui.html" line_range="275-293" />
<code_context>
+ });
+}
+
+async function uploadImageFromPaste(file) {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = async () => {
+ try {
+ const result = await apiRequest('UploadImage', { Image: reader.result });
+ if (!result.Success) {
+ reject(new Error(result.Message || '上传失败'));
+ return;
+ }
+ resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`);
+ } catch (error) {
+ reject(error);
+ }
+ };
+ reader.onerror = () => reject(new Error('读取图片失败'));
+ reader.readAsDataURL(file);
+ });
+}
</code_context>
<issue_to_address>
**suggestion (performance):** No file size or type validation when uploading pasted images could cause performance and UX issues.
Any pasted `image/*` blob is read fully as a data URL and uploaded without limits. Consider enforcing a reasonable max size (e.g., a few MB) and optionally restricting to common formats (PNG/JPEG) before calling `readAsDataURL` and `apiRequest` to avoid excessive memory and request payloads.
```suggestion
async function uploadImageFromPaste(file) {
const MAX_PASTE_IMAGE_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED_PASTE_IMAGE_TYPES = ['image/png', 'image/jpeg'];
return new Promise((resolve, reject) => {
if (!file || !file.type || !file.type.startsWith('image/')) {
reject(new Error('不支持的图片类型'));
return;
}
if (!ALLOWED_PASTE_IMAGE_TYPES.includes(file.type)) {
reject(new Error('仅支持 PNG 和 JPEG 图片'));
return;
}
if (file.size > MAX_PASTE_IMAGE_SIZE) {
reject(new Error('图片过大,请上传不超过 5MB 的图片'));
return;
}
const reader = new FileReader();
reader.onload = async () => {
try {
const result = await apiRequest('UploadImage', { Image: reader.result });
if (!result.Success) {
reject(new Error(result.Message || '上传失败'));
return;
}
resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`);
} catch (error) {
reject(error);
}
};
reader.onerror = () => reject(new Error('读取图片失败'));
reader.readAsDataURL(file);
});
}
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
webui.html
Outdated
| async function uploadImageFromPaste(file) { | ||
| return new Promise((resolve, reject) => { | ||
| const reader = new FileReader(); | ||
| reader.onload = async () => { | ||
| try { | ||
| const result = await apiRequest('UploadImage', { Image: reader.result }); | ||
| if (!result.Success) { | ||
| reject(new Error(result.Message || '上传失败')); | ||
| return; | ||
| } | ||
| resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`); | ||
| } catch (error) { | ||
| reject(error); | ||
| } | ||
| }; | ||
| reader.onerror = () => reject(new Error('读取图片失败')); | ||
| reader.readAsDataURL(file); | ||
| }); | ||
| } |
There was a problem hiding this comment.
suggestion (performance): No file size or type validation when uploading pasted images could cause performance and UX issues.
Any pasted image/* blob is read fully as a data URL and uploaded without limits. Consider enforcing a reasonable max size (e.g., a few MB) and optionally restricting to common formats (PNG/JPEG) before calling readAsDataURL and apiRequest to avoid excessive memory and request payloads.
| async function uploadImageFromPaste(file) { | |
| return new Promise((resolve, reject) => { | |
| const reader = new FileReader(); | |
| reader.onload = async () => { | |
| try { | |
| const result = await apiRequest('UploadImage', { Image: reader.result }); | |
| if (!result.Success) { | |
| reject(new Error(result.Message || '上传失败')); | |
| return; | |
| } | |
| resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`); | |
| } catch (error) { | |
| reject(error); | |
| } | |
| }; | |
| reader.onerror = () => reject(new Error('读取图片失败')); | |
| reader.readAsDataURL(file); | |
| }); | |
| } | |
| async function uploadImageFromPaste(file) { | |
| const MAX_PASTE_IMAGE_SIZE = 5 * 1024 * 1024; // 5MB | |
| const ALLOWED_PASTE_IMAGE_TYPES = ['image/png', 'image/jpeg']; | |
| return new Promise((resolve, reject) => { | |
| if (!file || !file.type || !file.type.startsWith('image/')) { | |
| reject(new Error('不支持的图片类型')); | |
| return; | |
| } | |
| if (!ALLOWED_PASTE_IMAGE_TYPES.includes(file.type)) { | |
| reject(new Error('仅支持 PNG 和 JPEG 图片')); | |
| return; | |
| } | |
| if (file.size > MAX_PASTE_IMAGE_SIZE) { | |
| reject(new Error('图片过大,请上传不超过 5MB 的图片')); | |
| return; | |
| } | |
| const reader = new FileReader(); | |
| reader.onload = async () => { | |
| try { | |
| const result = await apiRequest('UploadImage', { Image: reader.result }); | |
| if (!result.Success) { | |
| reject(new Error(result.Message || '上传失败')); | |
| return; | |
| } | |
| resolve(`https://assets.xmoj-bbs.me/GetImage?ImageID=${result.Data.ImageID}`); | |
| } catch (error) { | |
| reject(error); | |
| } | |
| }; | |
| reader.onerror = () => reject(new Error('读取图片失败')); | |
| reader.readAsDataURL(file); | |
| }); | |
| } |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 636b037711
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
webui.html
Outdated
| const data = await apiRequest('GetMail', { UserID: String(state.selectedUser) }); | ||
| if (!data.Success) { | ||
| throw new Error(data.Message || '获取消息失败'); | ||
| } | ||
| const list = data.Data?.Mails || []; |
There was a problem hiding this comment.
Align GetMail request/response keys with API contract
This call uses UserID and then reads data.Data?.Mails, but the existing short-message client in XMOJ.user.js calls GetMail with OtherUser and consumes ResponseData.Data.Mail (see lines 4686-4692 there). With the mismatched field names here, message fetches for a selected conversation can fail or render as empty even with a valid session.
Useful? React with 👍 / 👎.
webui.html
Outdated
| const setCookie = response.headers.get('set-cookie') || ''; | ||
| let sessionId = ''; | ||
|
|
||
| const match = setCookie.match(/PHPSESSID=([^;]+)/i); |
There was a problem hiding this comment.
Stop extracting session from forbidden Set-Cookie header
The login flow reads response.headers.get('set-cookie') to obtain PHPSESSID, but browsers do not expose Set-Cookie to JavaScript fetch responses, so sessionId stays empty and this path always falls into the error branch. As written, the “登录并获取会话” feature cannot actually complete and users are forced to manually paste the session ID.
Useful? React with 👍 / 👎.
|
你看,codex自己都觉得代码有问题(bushi |
| "Notes": "No release notes were provided for this release." | ||
| }, | ||
| "1.10.0": { | ||
| "1.999990.0": { |
|
他会自己修 |
There was a problem hiding this comment.
5 issues found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="README.md">
<violation number="1" location="README.md:78">
P2: This line overstates the login flow: the WebUI cannot auto-read `PHPSESSID` from a browser `fetch()` response, so username/password login won't obtain the session as documented.</violation>
</file>
<file name="webui.html">
<violation number="1" location="webui.html:8">
P1: This login page executes a third-party script without integrity pinning, which can expose usernames, passwords, and `PHPSESSID` if the CDN asset is tampered with.</violation>
<violation number="2" location="webui.html:253">
P1: The `GetMail` request uses `{ UserID: ... }` but the existing API (as used in `XMOJ.user.js`) expects the field to be named `OtherUser`. Similarly, the response is read as `data.Data?.Mails` (plural) but the API returns `Data.Mail` (singular). Both mismatches will cause message fetches to silently fail or return empty.</violation>
<violation number="3" location="webui.html:257">
P1: The response field `data.Data?.Mails` (plural) does not match the API's actual response shape `Data.Mail` (singular). This will always evaluate to `undefined`, falling back to an empty array, so no messages will ever be rendered.</violation>
<violation number="4" location="webui.html:314">
P1: `response.headers.get('set-cookie')` cannot retrieve `PHPSESSID` in browser code, so the automatic login flow will always fail to capture the session.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | ||
| <title>XMOJ 短消息 WebUI(实验)</title> | ||
| <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet"> | ||
| <script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script> |
There was a problem hiding this comment.
P1: This login page executes a third-party script without integrity pinning, which can expose usernames, passwords, and PHPSESSID if the CDN asset is tampered with.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webui.html, line 8:
<comment>This login page executes a third-party script without integrity pinning, which can expose usernames, passwords, and `PHPSESSID` if the CDN asset is tampered with.</comment>
<file context>
@@ -0,0 +1,425 @@
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <title>XMOJ 短消息 WebUI(实验)</title>
+ <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.min.css" rel="stylesheet">
+ <script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.19.0/js/md5.min.js"></script>
+ <style>
+ body { background: #f7f8fa; }
</file context>
webui.html
Outdated
| if (!data.Success) { | ||
| throw new Error(data.Message || '获取消息失败'); | ||
| } | ||
| const list = data.Data?.Mails || []; |
There was a problem hiding this comment.
P1: The response field data.Data?.Mails (plural) does not match the API's actual response shape Data.Mail (singular). This will always evaluate to undefined, falling back to an empty array, so no messages will ever be rendered.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webui.html, line 257:
<comment>The response field `data.Data?.Mails` (plural) does not match the API's actual response shape `Data.Mail` (singular). This will always evaluate to `undefined`, falling back to an empty array, so no messages will ever be rendered.</comment>
<file context>
@@ -0,0 +1,425 @@
+ if (!data.Success) {
+ throw new Error(data.Message || '获取消息失败');
+ }
+ const list = data.Data?.Mails || [];
+ el.messageList.innerHTML = '';
+ if (list.length === 0) {
</file context>
webui.html
Outdated
|
|
||
| await apiRequest('ReadUserMailMention', { UserID: String(state.selectedUser) }); | ||
|
|
||
| const data = await apiRequest('GetMail', { UserID: String(state.selectedUser) }); |
There was a problem hiding this comment.
P1: The GetMail request uses { UserID: ... } but the existing API (as used in XMOJ.user.js) expects the field to be named OtherUser. Similarly, the response is read as data.Data?.Mails (plural) but the API returns Data.Mail (singular). Both mismatches will cause message fetches to silently fail or return empty.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webui.html, line 253:
<comment>The `GetMail` request uses `{ UserID: ... }` but the existing API (as used in `XMOJ.user.js`) expects the field to be named `OtherUser`. Similarly, the response is read as `data.Data?.Mails` (plural) but the API returns `Data.Mail` (singular). Both mismatches will cause message fetches to silently fail or return empty.</comment>
<file context>
@@ -0,0 +1,425 @@
+
+ await apiRequest('ReadUserMailMention', { UserID: String(state.selectedUser) });
+
+ const data = await apiRequest('GetMail', { UserID: String(state.selectedUser) });
+ if (!data.Success) {
+ throw new Error(data.Message || '获取消息失败');
</file context>
|
@codex address review |
| } | ||
|
|
||
| function md5(input) { | ||
| return createHash('md5').update(input).digest('hex'); |
Check failure
Code scanning / CodeQL
Use of password hash with insufficient computational effort High
Copilot Autofix
AI 11 days ago
Copilot could not generate an autofix suggestion
Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.
| const response = await fetch(`https://api.xmoj-bbs.me/${action}`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify(body) | ||
| }); |
Check failure
Code scanning / CodeQL
Server-side request forgery Critical
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 11 days ago
In general, to fix this kind of issue you should never let raw user input determine the full path of an internal/backend request. Instead, map user inputs to a controlled set of allowed actions or validate them against a strict pattern, and then build the backend path from that sanitized value. This ensures that a client cannot cause the server to query unexpected endpoints, even if the hostname is fixed.
In this specific file, the problem is that action is directly derived from the path /__webui/api/<action> and then used verbatim in fetch(`https://api.xmoj-bbs.me/${action}`, ...). The safest fix without changing visible behavior too much is:
- Introduce a small validation/normalization function (inside this file) that:
- Rejects any
actioncontaining path traversal (..), backslashes, or leading/. - Optionally constrains it to a simple character set (e.g., letters, digits,
/_-).
- Rejects any
- Call this validator both at the routing point (to fail fast) and inside
handleApiProxy(defense in depth). - If validation fails, return a
400error with a simple JSON error message instead of performing thefetch. - Keep the scheme and hostname unchanged and still allow multi-segment paths like
foo/barif needed by the existing API.
Concretely:
- Add a helper function
sanitizeAction(action)abovehandleApiProxy. - In
createServerwhereactionis extracted (around lines 135–137), passactionthroughsanitizeAction; if it returnsnull, respond with400. - In
handleApiProxy, re-sanitizeactionand reject if invalid, then use the sanitized value to construct the URL.
No new imports are needed; we can implement this logic with plain JavaScript.
| @@ -88,9 +88,22 @@ | ||
| }); | ||
| } | ||
|
|
||
| function sanitizeAction(action) { | ||
| if (typeof action !== 'string') return null; | ||
| // Disallow absolute paths or path traversal | ||
| if (action.startsWith('/') || action.includes('..') || action.includes('\\')) return null; | ||
| // Allow only safe characters: letters, digits, underscore, hyphen, slash | ||
| if (!/^[A-Za-z0-9_\/-]+$/.test(action)) return null; | ||
| return action; | ||
| } | ||
|
|
||
| async function handleApiProxy(req, res, action) { | ||
| const safeAction = sanitizeAction(action); | ||
| if (!safeAction) { | ||
| return json(res, 400, { success: false, message: '非法 action' }); | ||
| } | ||
| const body = await readBody(req); | ||
| const response = await fetch(`https://api.xmoj-bbs.me/${action}`, { | ||
| const response = await fetch(`https://api.xmoj-bbs.me/${safeAction}`, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify(body) | ||
| @@ -134,7 +145,9 @@ | ||
| if (pathname.startsWith('/__webui/api/') && req.method === 'POST') { | ||
| const action = pathname.substring('/__webui/api/'.length); | ||
| if (!action) return json(res, 400, { success: false, message: '缺少 action' }); | ||
| return await handleApiProxy(req, res, action); | ||
| const safeAction = sanitizeAction(action); | ||
| if (!safeAction) return json(res, 400, { success: false, message: '非法 action' }); | ||
| return await handleApiProxy(req, res, safeAction); | ||
| } | ||
|
|
||
| return await serveStatic(req, res, pathname); |
Summary
PR / Commit
Testing
|
Deploying xmoj-script-dev-channel with
|
| Latest commit: |
636b037
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://2ace9192.xmoj-script-dev-channel.pages.dev |
| Branch Preview URL: | https://codex-github-mention-follow.xmoj-script-dev-channel.pages.dev |
There was a problem hiding this comment.
4 issues found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tools/webui-proxy.mjs">
<violation number="1" location="tools/webui-proxy.mjs:7">
P1: Default this proxy to loopback. Binding to `0.0.0.0` exposes the served working tree to the LAN by default.</violation>
<violation number="2" location="tools/webui-proxy.mjs:9">
P2: Resolve static files from the script/repository directory instead of `process.cwd()`, or the proxy serves the wrong tree when launched from another directory.</violation>
</file>
<file name="README.md">
<violation number="1" location="README.md:80">
P3: This line understates the limitation: direct mode username/password login is not merely unstable, it is unsupported by the current implementation.</violation>
</file>
<file name="webui.html">
<violation number="1" location="webui.html:239">
P2: The direct-mode login branch can fail with a raw CORS/network error before your explanatory message runs. Skip the cross-origin login attempt here and fail fast with the guidance message instead.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| import { createHash } from 'node:crypto'; | ||
| import { extname, join, normalize } from 'node:path'; | ||
|
|
||
| const HOST = process.env.HOST || '0.0.0.0'; |
There was a problem hiding this comment.
P1: Default this proxy to loopback. Binding to 0.0.0.0 exposes the served working tree to the LAN by default.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/webui-proxy.mjs, line 7:
<comment>Default this proxy to loopback. Binding to `0.0.0.0` exposes the served working tree to the LAN by default.</comment>
<file context>
@@ -0,0 +1,146 @@
+import { createHash } from 'node:crypto';
+import { extname, join, normalize } from 'node:path';
+
+const HOST = process.env.HOST || '0.0.0.0';
+const PORT = Number(process.env.PORT || 8787);
+const ROOT = process.cwd();
</file context>
|
|
||
| const HOST = process.env.HOST || '0.0.0.0'; | ||
| const PORT = Number(process.env.PORT || 8787); | ||
| const ROOT = process.cwd(); |
There was a problem hiding this comment.
P2: Resolve static files from the script/repository directory instead of process.cwd(), or the proxy serves the wrong tree when launched from another directory.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tools/webui-proxy.mjs, line 9:
<comment>Resolve static files from the script/repository directory instead of `process.cwd()`, or the proxy serves the wrong tree when launched from another directory.</comment>
<file context>
@@ -0,0 +1,146 @@
+
+const HOST = process.env.HOST || '0.0.0.0';
+const PORT = Number(process.env.PORT || 8787);
+const ROOT = process.cwd();
+
+const MIME = {
</file context>
| const params = new URLSearchParams(); | ||
| params.set('user_id', username); | ||
| params.set('password', md5(password)); | ||
| await fetch('https://www.xmoj.tech/login.php', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, | ||
| body: params.toString(), | ||
| credentials: 'include' | ||
| }); | ||
| throw new Error('直连模式下浏览器无法读取跨域 Set-Cookie,请手动填写 PHPSESSID,或改用代理模式。'); |
There was a problem hiding this comment.
P2: The direct-mode login branch can fail with a raw CORS/network error before your explanatory message runs. Skip the cross-origin login attempt here and fail fast with the guidance message instead.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At webui.html, line 239:
<comment>The direct-mode login branch can fail with a raw CORS/network error before your explanatory message runs. Skip the cross-origin login attempt here and fail fast with the guidance message instead.</comment>
<file context>
@@ -168,59 +167,101 @@ <h2 class="h5">4) 发送消息 / 图片</h2>
}
- return response.json();
+
+ const params = new URLSearchParams();
+ params.set('user_id', username);
+ params.set('password', md5(password));
</file context>
| const params = new URLSearchParams(); | |
| params.set('user_id', username); | |
| params.set('password', md5(password)); | |
| await fetch('https://www.xmoj.tech/login.php', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, | |
| body: params.toString(), | |
| credentials: 'include' | |
| }); | |
| throw new Error('直连模式下浏览器无法读取跨域 Set-Cookie,请手动填写 PHPSESSID,或改用代理模式。'); | |
| throw new Error('直连模式下浏览器无法读取跨域 Set-Cookie,请手动填写 PHPSESSID,或改用代理模式。'); |
|
|
||
| - 推荐启动代理模式:`node tools/webui-proxy.mjs`,然后访问 `http://127.0.0.1:8787/webui.html`。 | ||
| - 代理模式下支持直接输入用户名/密码登录并自动获取 `PHPSESSID`;同时也支持手动粘贴 `PHPSESSID`。 | ||
| - 直连模式(直接打开页面)受浏览器跨域限制,账号密码登录可能不可用,此时建议改用代理模式。 |
There was a problem hiding this comment.
P3: This line understates the limitation: direct mode username/password login is not merely unstable, it is unsupported by the current implementation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 80:
<comment>This line understates the limitation: direct mode username/password login is not merely unstable, it is unsupported by the current implementation.</comment>
<file context>
@@ -75,8 +75,10 @@
-- 安全策略:密码仅用于发起登录请求,不会保存;会话信息仅存储在当前标签页的 `sessionStorage`,可一键清除。
+- 推荐启动代理模式:`node tools/webui-proxy.mjs`,然后访问 `http://127.0.0.1:8787/webui.html`。
+- 代理模式下支持直接输入用户名/密码登录并自动获取 `PHPSESSID`;同时也支持手动粘贴 `PHPSESSID`。
+- 直连模式(直接打开页面)受浏览器跨域限制,账号密码登录可能不可用,此时建议改用代理模式。
+- 安全策略:密码仅用于发起登录请求,不会在前端存储;会话信息仅存储在当前标签页的 `sessionStorage`,可一键清除。
- 维护策略:API 调用格式与现有脚本中的 `RequestAPI` 保持一致,便于后续复用和统一维护。
</file context>
| - 直连模式(直接打开页面)受浏览器跨域限制,账号密码登录可能不可用,此时建议改用代理模式。 | |
| - 直连模式(直接打开页面)受浏览器跨域限制,账号密码登录不可用,此时请手动填写 `PHPSESSID`,或改用代理模式。 |
|
What a mess |
|
I am clauding |
|
你可以看看我用copilot做的那个,里面有一个和你奇妙的登录功能 |
Motivation
xmoj.techwithout installing the userscript, and make login easier for users who don't know how to extractPHPSESSID.PHPSESSID) while keeping credentials transient and session data tab-scoped.Description
webui.html, an experimental single-file client that supports username/password login (usesmd5(password)as in the userscript), manualPHPSESSIDfallback, conversation list (GetMailList), message viewing (GetMail+ReadUserMailMention), sending (SendMail), and paste-to-upload image support (UploadImage).sessionStorage(tab-scoped) and exposes a clear button to remove the session, and it formats API payloads to match the userscriptRequestAPIshape for compatibility.index.htmland documented the experimental feature, usage and security/maintenance notes inREADME.md.Testing
git diff --checkwith no whitespace or obvious diff errors reported.webui.html, and a full-page screenshot was captured via Playwright to confirm the UI renders correctly (artifact saved).api.xmoj-bbs.meendpoints and will require a validPHPSESSIDfor live end-to-end verification.Codex Task
Summary by Sourcery
Introduce an experimental browser-based WebUI for viewing and sending short messages without installing the userscript, and link it from the project homepage.
New Features:
Enhancements:
Documentation:
Summary by cubic
Adds an experimental WebUI for short messages with session-based login, now with an optional local proxy so password login and API calls work reliably without CORS issues. Users can read/send messages and upload images on xmoj.tech without installing the userscript.
webui.html: username/password login (md5(password)) with manualPHPSESSIDfallback; conversations (GetMailList), messages (GetMail+ReadUserMailMention), sending (SendMail), paste-to-upload images (UploadImage); auto-detects proxy vs direct mode and uses/__webui/login+/__webui/api/*when proxied; tab-scopedsessionStoragewith one-click clear; API payloads matchRequestAPI.tools/webui-proxy.mjs: tiny local server (node tools/webui-proxy.mjs, defaults tohttp://127.0.0.1:8787) that serves static files and provides/__webui/health,/__webui/login(sets/returnsPHPSESSID), and/__webui/api/:actionto forward requests tohttps://api.xmoj-bbs.me/.index.html: adds a navbar link to the WebUI.README.md: documents proxy mode usage, direct mode limitations, and security/maintenance notes.Written for commit b7c77e6. Summary will update on new commits.