diff --git a/docs/API_INTEGRATION.md b/docs/API_INTEGRATION.md index ed6f061..2de2f48 100644 --- a/docs/API_INTEGRATION.md +++ b/docs/API_INTEGRATION.md @@ -257,3 +257,25 @@ type LoginReq struct { `GetDefaultRoleID` 回填(见 `UpdateUser` 同款逻辑),若客户已有 role id 应显式传入。 - 相关常量:`GENERAL=0, GUEST=1, ADMIN=2, NEWGENERAL=3`(`internal/model/user.go`);`permission` bitmask 定义同文件注释(bit 14 = "check path limit" = 16384)。 + +### 9.8 ✅ 正确结论:role `permission_scopes` + `base_path="/"` = 多文件夹授权(已实测确认) + +**最终确认的多文件夹授权正确做法**(2026-08-22 实测 mama 账号验证通过): + +- **组合**:`base_path = "/"`(根)+ role `permission_scopes` 用**绝对路径**(`/Class/Forex`、`/Class/Health`)。 +- **实测结果**(mama, base_path=`/`, role scopes=`[/Class/Forex, /Class/Health]`): + - `fs list /` → 只显示被授权的挂载点(`Class`),**不泄露** `/Book`/`/Comic` 等未授权挂载点 ✅ + - `fs list /Class/Forex` → 200 ✅、`fs list /Class/Health` → 200 ✅(**第二个 scope 也生效**) + - `fs list /Class/Other` → 403 ✅、`fs list /Book` → 403 ✅(未授权正确拒绝) +- **关键坑(之前失败的原因)**:`base_path` 若设成 `/Class`(而非 `/`),scope 绝对路径 `/Class/Forex` + 会被 `JoinPath` 拼成 `/Class/Class/Forex` → 全部 403。**必须 `/`**。 + - 注意:`CreateUser` 里有 `u.BasePath = scopes[0].Path` 的覆盖逻辑,但仅当 role **有 scope** 时触发; + 若创建时先建空 role(无 scope),base_path 不会被覆盖。W1 顺序:先建空 role → 建 user + (base_path=`/`)→ W2 再填 scope。实测当 base_path 被显式设为 `/` 且 role 后续填 scope 时, + 重新 login/访问正常。 +- **不需要 Alias storage**(§9.9 曾是备选,现已确认 role scope 路线够用且更简单)。 + +### 9.9(备选,未采用)Alias storage —— 一用户看多分散文件夹的另一种方式 + +(保留作为参考:`drivers/alias/driver.go`,`paths` 换行 `别名:目标`,`writable:false` 只读。 +但既然 role scope + base_path=`/` 已能满足,除非有「合并路径语义/去重/隔离」特殊需求,否则不必上 Alias。) diff --git a/docs/PROJECT_STATE.md b/docs/PROJECT_STATE.md index e23b8c6..3284301 100644 --- a/docs/PROJECT_STATE.md +++ b/docs/PROJECT_STATE.md @@ -53,6 +53,8 @@ Base: **`AlistAccess`**(`p3lzkresswyowqg`),与旧 `accessDB` 完全隔离 - delete 用 `?id=` query param,不是 body - role/list 返回 `data.content[]` + `data.total`(PageResp) - GUEST=1 / ADMIN=2 / GENERAL=0;用户级 bit14=16384 = Check path limit + - **多文件夹授权正确做法(实测确认)**:`base_path="/"` + role scope 用**绝对路径**。base_path 设成 + `/Class` 会导致 scope 绝对路径被 JoinPath 双重前缀 → 全部 403。见 API_INTEGRATION §9.8。 - 源码位置:`internal/op/user.go` CreateUser / `server/handles/role.go` / `internal/model/roles.go` - **NocoDB**(`nocodb-selfhosted` skill 已更新): - SingleSelect 不能建表内联选项;要先建空列再 PATCH `{"colOptions":{options},"dtxp":"a,b,c"}` diff --git a/n8n/W1-customer-provision.json b/n8n/W1-customer-provision.json index cef76aa..d433515 100644 --- a/n8n/W1-customer-provision.json +++ b/n8n/W1-customer-provision.json @@ -77,7 +77,7 @@ }, { "parameters": { - "jsCode": "const cust = $input.item.json;\nconst username = cust.username; const password = cust.password;\nif (!username) throw new Error('Customer missing username');\nconst newUserId = cust.alist_user_id ?? null;\nconst newRoleId = cust.alist_role_id ?? null;\nconst isCreate = !newUserId;\n// Break loop: only write back if semantic fields would actually change.\n// If the record already has alist_user_id + role_id + sync_status=synced,\n// this webhook was fired by our own write-back (echo) -> skip.\nconst skipWriteBack = (!isCreate) && (cust.sync_status === 'synced');\nreturn { json: {\n customerId: cust.Id ?? cust.id,\n username, password,\n displayName: cust.display_name ?? username,\n basePath: cust.base_path ?? '/Class',\n alistUserId: newUserId,\n alistRoleId: newRoleId,\n isCreate,\n skipWriteBack,\n} };\n" + "jsCode": "const cust = $input.item.json;\nconst username = cust.username; const password = cust.password;\nif (!username) throw new Error('Customer missing username');\nconst newUserId = cust.alist_user_id ?? null;\nconst newRoleId = cust.alist_role_id ?? null;\nconst isCreate = !newUserId;\n// Break loop: only write back if semantic fields would actually change.\n// If the record already has alist_user_id + role_id + sync_status=synced,\n// this webhook was fired by our own write-back (echo) -> skip.\nconst skipWriteBack = (!isCreate) && (cust.sync_status === 'synced');\nreturn { json: {\n customerId: cust.Id ?? cust.id,\n username, password,\n displayName: cust.display_name ?? username,\n basePath: '/',\n alistUserId: newUserId,\n alistRoleId: newRoleId,\n isCreate,\n skipWriteBack,\n} };\n" }, "id": "prepare", "name": "Prepare Provision Payload",