From 884e819f70c8104452cb5eda7c8e1915ebe2ce8e Mon Sep 17 00:00:00 2001 From: john Date: Thu, 27 Aug 2026 09:54:46 +0800 Subject: [PATCH] fix(mindspace): lower page access password minimum to 6 characters Align publication validation, portal gate HTML, H5 publish UI, MCP docs, and page-data skill guidance so passwords like 888888 are accepted while 3-character values such as 888 remain rejected. Co-authored-by: Cursor --- mindspace-publications.mjs | 17 ++++++++++++++--- mindspace-publications.test.mjs | 1 + mindspace-sandbox-mcp.mjs | 2 +- page-data-validation-suggestions.mjs | 2 +- page-data-workspace-bind.mjs | 2 +- server/portal-publication-routes.mjs | 2 +- skills/page-data-collect/SKILL.md | 18 +++++++++--------- src/components/MindSpacePageDataOpsPanel.tsx | 4 ++-- src/components/MindSpacePageDetail.tsx | 2 +- 9 files changed, 31 insertions(+), 19 deletions(-) diff --git a/mindspace-publications.mjs b/mindspace-publications.mjs index 28c742b..c499d15 100644 --- a/mindspace-publications.mjs +++ b/mindspace-publications.mjs @@ -78,11 +78,20 @@ function normalizeAccessMode(value) { return mode; } +export const PAGE_ACCESS_PASSWORD_MIN_LENGTH = 6; +export const PAGE_ACCESS_PASSWORD_MAX_LENGTH = 128; + function normalizePassword(value, required) { const password = String(value ?? ''); if (!required && !password) return null; - if (password.length < 8 || password.length > 128) { - throw publicationError('访问密码长度必须为 8 到 128 个字符', 'invalid_publish_input'); + if ( + password.length < PAGE_ACCESS_PASSWORD_MIN_LENGTH + || password.length > PAGE_ACCESS_PASSWORD_MAX_LENGTH + ) { + throw publicationError( + `访问密码长度必须为 ${PAGE_ACCESS_PASSWORD_MIN_LENGTH} 到 ${PAGE_ACCESS_PASSWORD_MAX_LENGTH} 个字符`, + 'invalid_publish_input', + ); } return password; } @@ -1083,7 +1092,7 @@ export function createPublicationService(pool, options = {}) { } else if (publication.password_hash) { passwordHash = publication.password_hash; } else { - throw publicationError('访问密码长度必须为 8 到 128 个字符', 'invalid_publish_input'); + throw publicationError('口令访问模式必须提供访问密码', 'invalid_publish_input'); } } const now = Date.now(); @@ -1498,6 +1507,8 @@ async function registerPublicationArtifactForConversation({ } export const publicationInternals = { + PAGE_ACCESS_PASSWORD_MIN_LENGTH, + PAGE_ACCESS_PASSWORD_MAX_LENGTH, normalizeSlug, normalizeAccessMode, normalizePassword, diff --git a/mindspace-publications.test.mjs b/mindspace-publications.test.mjs index 0ad16e4..9545d41 100644 --- a/mindspace-publications.test.mjs +++ b/mindspace-publications.test.mjs @@ -110,6 +110,7 @@ test('hashes access passwords with a random salt', () => { () => publicationInternals.normalizePassword('short', true), (error) => error.code === 'invalid_publish_input', ); + assert.equal(publicationInternals.normalizePassword('888888', true), '888888'); }); test('requires future expiry timestamps for time-limited pages', () => { diff --git a/mindspace-sandbox-mcp.mjs b/mindspace-sandbox-mcp.mjs index 931a0fc..4dec1bc 100644 --- a/mindspace-sandbox-mcp.mjs +++ b/mindspace-sandbox-mcp.mjs @@ -473,7 +473,7 @@ const ALL_TOOLS = [ password: { type: 'string', description: - '口令访问密码,accessMode=password 时可选;未提供则默认 88888888(至少 8 位)', + '口令访问密码,accessMode=password 时可选;未提供则默认 88888888(至少 6 位)', }, urlSlug: { type: 'string', description: '发布 URL slug,可选' }, datasets: { diff --git a/page-data-validation-suggestions.mjs b/page-data-validation-suggestions.mjs index 24773af..595940c 100644 --- a/page-data-validation-suggestions.mjs +++ b/page-data-validation-suggestions.mjs @@ -45,7 +45,7 @@ const SUGGESTION_BY_CODE = Object.freeze({ title: '页面数据绑定或发布失败', actions: Object.freeze([ '确认 dataset 已 private_data_register_dataset 注册', - '对每个公开页调用 private_data_bind_workspace_page(含 password 策略时口令 ≥8 位)', + '对每个公开页调用 private_data_bind_workspace_page(含 password 策略时口令 ≥6 位)', '检查 .mindspace/page-data-policies 是否生成且列白名单与表单字段一致', ]), }), diff --git a/page-data-workspace-bind.mjs b/page-data-workspace-bind.mjs index d8b4bd1..907a315 100644 --- a/page-data-workspace-bind.mjs +++ b/page-data-workspace-bind.mjs @@ -22,7 +22,7 @@ export function resolvePageDataBindAccess(accessMode = 'public') { return publicationInternals.normalizeAccessMode(accessMode); } -/** password 模式:用户未传口令时使用 DEFAULT_PAGE_DATA_ADMIN_PASSWORD;<8 位抛错 */ +/** password 模式:用户未传口令时使用 DEFAULT_PAGE_DATA_ADMIN_PASSWORD;<6 位抛错 */ export function resolvePageDataBindPassword(accessMode, password = null) { const mode = resolvePageDataBindAccess(accessMode); if (mode !== 'password') return null; diff --git a/server/portal-publication-routes.mjs b/server/portal-publication-routes.mjs index d158e55..d6b23cd 100644 --- a/server/portal-publication-routes.mjs +++ b/server/portal-publication-routes.mjs @@ -39,7 +39,7 @@ export function renderPublicationPasswordGate(

此页面受密码保护

请输入发布者提供的访问密码。密码不会写入链接或浏览器日志。

- +
diff --git a/skills/page-data-collect/SKILL.md b/skills/page-data-collect/SKILL.md index 6f88f98..9fab01b 100644 --- a/skills/page-data-collect/SKILL.md +++ b/skills/page-data-collect/SKILL.md @@ -46,7 +46,7 @@ HTML 页面只能调用 Page Data API(page-data-client.js); |----|--------| | 访客 | 匿名提交(`public`,仅 `insert`) | | 后台 | **独立** HTML 页(`password`,仅 `read`) | -| 后台口令 | **`88888888`**(平台要求 8~128 位;用户可指定其它合法口令覆盖) | +| 后台口令 | **`88888888`**(平台要求 6~128 位;用户可指定其它合法口令覆盖,如 `888888`) | | 提交后修改 | 不支持(一次性) | | 页面 | `public/*-survey.html` + `public/*-admin.html`(两文件各 bind 一次) | @@ -54,7 +54,7 @@ HTML 页面只能调用 Page Data API(page-data-client.js); 口令规则: -- **禁止**接受或使用 <8 位口令(如 `888`);若用户坚持短口令,说明平台限制并代用 `88888888` 或请其给出 ≥8 位。 +- **禁止**接受或使用 <6 位口令(如 `888`);若用户坚持更短口令,说明平台限制并代用 `888888` / `88888888` 或请其给出 ≥6 位。 - bind 时 **`password` 必须传入**且会写入发布记录;禁止只改 `access_mode` 不写 `password_hash`。 - 交付说明写明后台口令;用户可在 MindSpace「页面数据」面板重置。 @@ -73,7 +73,7 @@ HTML 页面只能调用 Page Data API(page-data-client.js); **仅以下情况才问用户(每次最多 1~2 点)**: 1. 表述同时命中两个互斥分支(如「匿名提交」+「同页内嵌口令看全量」)→ 给 A/E 选项说明须拆页 -2. 用户明确要的口令 <8 位 → 请改口令或确认用默认 `88888888` +2. 用户明确要的口令 <6 位 → 请改口令或确认用默认 `88888888` 3. 需要 B/C 但表结构是否要 `created_by_user_id` 等列尚不清楚 → 确认登录隔离 **禁止**连续多轮只输出「我先检查工作区/加载技能」而不调用工具;**第一轮工具**应是 `load_skill` 或 `list_dir` / `private_data_execute`,不是空计划。 @@ -82,7 +82,7 @@ HTML 页面只能调用 Page Data API(page-data-client.js); ```text - public:可匿名 insert;服务端禁止 update / softDelete -- password:所有 API 须先 authenticate;口令 ≥8 位且须写入发布记录 +- password:所有 API 须先 authenticate;口令 ≥6 位且须写入发布记录 - `public` 策略中的 `insert: true` 等价于任何访客都能直接调用 API 写入;前端口令、隐藏按钮和 JavaScript 判断都不是权限控制 - password 页面必须调用 `client.authenticate(password)` 获取服务端 token;禁止在 HTML 中硬编码口令或只做 `password === "..."` 比较 - 同一公开页若要求「所有人可评价、只有所有者可写正文」:正文 dataset 在公开页必须 `insert: false`,评价 dataset 才能 `insert: true`;所有者写正文必须使用独立 `password` 作者页或 `login_required` 页面 @@ -99,7 +99,7 @@ HTML 页面只能调用 Page Data API(page-data-client.js); - 分支:A 匿名提交 + 独立口令后台 - 访客:匿名提交(public,仅 insert) - 管理员:独立后台页(password,仅 read) -- 后台口令:88888888(可改为你的 ≥8 位口令) +- 后台口令:88888888(可改为你的 ≥6 位口令,如 888888) - 提交后修改:不支持 - 页面:public/xxx.html + public/xxx-admin.html - 内容:(由你的描述生成题目/字段/报表) @@ -283,7 +283,7 @@ await client.deleteRow('dataset_name', rowId); 若问卷与后台是多个 HTML 文件,对每个 `public/*.html` 各调用一次 bind: - **问卷页**:`accessMode: "public"` + dataset 仅 `insert` -- **后台页**:`accessMode: "password"` + dataset 仅 `read`(发布口令至少 8 位) +- **后台页**:`accessMode: "password"` + dataset 仅 `read`(发布口令至少 6 位) **顺序**:先 `write_file` 完整 HTML → 再 `bind`;禁止只写「问卷页面」占位文字就发布。 @@ -301,7 +301,7 @@ await client.deleteRow('dataset_name', rowId); // pageId 可省略:平台访问时注入 __MINDSPACE_PAGE_DATA__ const client = MindSpacePageData.createClient({ apiBase: '/api' }); - // 口令页后台:await client.authenticate('88888888'); // 口令 ≥8 位,与发布时一致 + // 口令页后台:await client.authenticate('88888888'); // 口令 ≥6 位,与发布时一致 // 提交:await client.insertRow('survey_responses', { q1_feature: '...', ... }); // 列表:const { rows } = await client.listRows('survey_responses', { limit: 50 }); @@ -324,7 +324,7 @@ await client.deleteRow('dataset_name', rowId); 1. **禁止**在尚未获得创建授权时建表 / bind / 发布;用户明确要求「创建、完成、发布、直接做、不用询问、持续推进」均视为确认,禁止再次停下来询问 2. **禁止**让 LLM 自行发明 accessMode/拆页/口令;必须落在分支 A~E 与默认口令规则内 3. **禁止**连续两轮仅输出计划、不调用 `load_skill` / `list_dir` / `write_file` / `private_data_execute` / `bind` -4. **禁止**接受 <8 位发布口令;用户未指定口令时后台默认 **`88888888`** +4. **禁止**接受 <6 位发布口令;用户未指定口令时后台默认 **`88888888`** 5. **禁止**创建 `scripts/*-api.mjs`、Express 服务、或监听独立端口(如 `8899`) 6. **禁止** HTML 中硬编码 `http://127.0.0.1:端口` 或自定义 `/api/survey/*` 7. **禁止**在 HTML 中使用 `onclick` / `oninput` 等内联事件属性(MindSpace 发布页 CSP 不允许);改用 `addEventListener` @@ -340,7 +340,7 @@ await client.deleteRow('dataset_name', rowId); ## 交付前自检 -0. 已选定分支 A~E,方案摘要已确认;`password` 页口令 ≥8 位且 bind 已传入 +0. 已选定分支 A~E,方案摘要已确认;`password` 页口令 ≥6 位且 bind 已传入 1. `private_data_register_dataset` 已注册;PostgreSQL 表字段与 policy 一致(含 `deleted_at` 等) 2. `private_data_bind_workspace_page` **已成功**,返回 UUID 形式 `pageId` 与 `deliveryUrl` 3. `.mindspace/page-data-policies/.json` 已写入(文件名必须是 UUID,不是 slug) diff --git a/src/components/MindSpacePageDataOpsPanel.tsx b/src/components/MindSpacePageDataOpsPanel.tsx index d6e31a7..5919b03 100644 --- a/src/components/MindSpacePageDataOpsPanel.tsx +++ b/src/components/MindSpacePageDataOpsPanel.tsx @@ -401,12 +401,12 @@ export function MindSpacePageDataOpsPanel({ pageId, pageTitle, accessMode, onClo type="password" value={newPassword} onChange={(event) => setNewPassword(event.target.value)} - placeholder="新页面口令(至少 8 位)" + placeholder="新页面口令(至少 6 位)" autoComplete="new-password" />