Files
alist-serve-resource/docs/API_INTEGRATION.md
T

260 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AList API 集成说明(源码级验证)
> 规格文档 §11/§47 要求:不猜 API,不发明 endpoint。本文所有端点均已从
> AList 上游源码(`AlistGo/alist`,`v3.63.0`)验证,而非仅凭文档目录猜测。
## 1. 版本
- 目标版本:`xhofe/alist:v3.63.0`(2026-08 最新稳定 release)
- Role-scoped path aggregation + Check path limit 自 v3.53.0 起支持(规格 §48 要求已满足)。
## 2. 认证
```
POST /api/auth/login
Body: { "username": "admin", "password": "***" }
响应: { "code": 200, "data": { "token": "<JWT>" } }
```
后续所有 Admin 请求带 header:
```
Authorization: <token>
```
## 3. 已验证的 Admin API
源码位置:`server/router.go`(`admin(g *gin.RouterGroup)`)、`server/handles/user.go`、`server/handles/role.go`。
### 3.1 User API
| 端点 | 方法 | 说明 |
|---|---|---|
| `/api/admin/user/list` | GET | 分页列出用户(`?page=&per_page=`) |
| `/api/admin/user/get` | GET | `?id=` 取单个用户 |
| `/api/admin/user/create` | POST | 建用户 |
| `/api/admin/user/update` | POST | 改用户 |
| `/api/admin/user/delete` | POST | `?id=` 删用户 |
| `/api/admin/user/cancel_2fa` | POST | 撤销某用户 2FA |
### 3.2 Role API(关键 —— 完整 CRUD 存在)
| 端点 | 方法 | 说明 |
|---|---|---|
| `/api/admin/role/list` | GET | 分页列出 role |
| `/api/admin/role/get` | GET | `?id=` 取单个 role |
| `/api/admin/role/create` | POST | 建 role |
| `/api/admin/role/update` | POST | 改 role(含 `permission_scopes`) |
| `/api/admin/role/delete` | POST | `?id=` 删 role |
> ✅ 结论:Role scope 的增删改**可通过官方 API 完整实现**,无需 fork、无需
> integration service(规格 §12 的 Option C 不需要走)。
## 4. 数据结构(源码 `internal/model/`)
### 4.1 Role
```go
type Role struct {
ID uint `json:"id"`
Name string `json:"name"` // 唯一
Description string `json:"description"`
Default bool `json:"default"`
PermissionScopes []PermissionEntry `json:"permission_scopes"`
}
type PermissionEntry struct {
Path string `json:"path"` // 路径前缀,如 "/Class/A/1"
Permission int32 `json:"permission"` // bitmask
}
```
JSON 示例:
```json
{
"id": 5,
"name": "customer-alice",
"description": "Alice customer role",
"default": false,
"permission_scopes": [
{ "path": "/Class/A/1", "permission": 0 },
{ "path": "/Class/A/2", "permission": 0 }
]
}
```
### 4.2 User
```go
type User struct {
ID uint `json:"id"`
Username string `json:"username"`
Password string `json:"password"` // create/update 时传入明文
BasePath string `json:"base_path"`
Role Roles `json:"role"` // role-ID 数组
Permission int32 `json:"permission"` // bitmask
Disabled bool `json:"disabled"`
}
```
JSON 示例(只读客户):
```json
{
"id": 10,
"username": "alice",
"password": "***",
"base_path": "/Class",
"role": [5],
"permission": 16384,
"disabled": false
}
```
## 5. Permission bitmask(源码 `internal/model/user.go`)
| bit | 含义 | 对应 |
|---|---|---|
| 0 | 查看隐藏文件 | 1 |
| 1 | 免密访问 | 2 |
| 2 | 离线下载任务 | 4 |
| 3 | mkdir / upload | 8 |
| 4 | rename | 16 |
| 5 | move | 32 |
| 6 | copy | 64 |
| 7 | remove / delete | 128 |
| 8 | WebDAV read | 256 |
| 9 | WebDAV write | 512 |
| 10 | FTP login/read | 1024 |
| 11 | FTP write | 2048 |
| 12 | 读压缩包 | 4096 |
| 13 | 解压 | 8192 |
| **14** | **Check path limit** | **16384** |
| 15 | MCP read | 32768 |
| 16 | MCP write | 65536 |
**本项目默认:**
- `read-only` = bitmask **`0`**(不设 bit 3–7/9/11/13 等任何写权限)
- `Check path limit ON` = 设 bit 14 → **`16384`**
即客户 user 的 `permission` 字段 = `16384`(只读 + 路径限制),所有写位为 0。
## 6. Role scope + Check path limit 的权限判定逻辑
源码 `JoinPath()`(`internal/model/user.go`)证实:
1. 用户 base path 只是登录后的默认落点,**不是 ACL**(规格 §2/§30)。
2. 若用户 `CheckPathLimit()` 为真(bit 14 置位),请求路径必须落在该用户**任一 role
的任一 permission scope 的 path 之内**(前缀匹配),否则返回 `PermissionDenied`。
3. 因此「Alice 只能进 /Class/A/1 与 /Class/A/2,进不了 /Class/A/3 与 /Class/B」
的正确实现 = Alice 专属 role 的 `permission_scopes` 只含这两个 path,且 user
`permission` bit 14 置位。
## 7. 保护性约束(源码确认)
- `admin` / `guest` role 受保护:`update`(改 admin 名/默认属性)与 `delete` 会被
`ErrChangeDefaultRole` 拒绝。客户 role 不受此限。
- `CreateUser` 时若 `role` 空数组,会回填默认 role ID(`op.GetDefaultRoleID()`)。
**因此建客户时必须显式传其专属 role ID,不能留空。**
- `UpdateUser` 若试图把用户 role 改成 admin/guest role,会被拒绝。
- 至少保留 1 个 enabled admin,否则禁用最后一个 admin 会失败。
## 8. 给 n8n 的实现要点(幂等,规格 §10/§28)
- 建用户:先 `GET /api/admin/user/list` 按 `username` 查重,存在则 `update`,否则 `create`。
- 建 role:先 `GET /api/admin/role/list` 按 `name` 查重,存在则 `update`(重写 `permission_scopes`),否则 `create`。
- 更新 scope:`POST /api/admin/role/update`,整体重写该 role 的 `permission_scopes`
(desired state = NocoDB 当前有效 grants 的并集),天然幂等。
- 到期撤销:从 desired scopes 里移除过期 path,再整体 `update` 该 role。
- 禁用客户:`POST /api/admin/user/update` 设 `disabled: true`。
- 删除客户:soft delete(NocoDB 侧 `status=deleted`),AList 侧禁用用户 + 清空 role scopes,不物理删除(保留审计)。
## 9. Phase 3 实测验证(2026-08-22,对真实 v3.63.0 实例)
> 以下为对 DSM 上运行中的 `alist-serve-resource`(v3.63.0,端口 5244)实际 curl 验证结果,
> 修正/确认了上文的若干推断。
### 9.1 认证 —— 登录带 2FA 时的字段
源码 `server/handles/auth.go` 的 `LoginReq` 结构:
```go
type LoginReq struct {
Username string `json:"username" binding:"required"`
Password string `json:"password"`
OtpCode string `json:"otp_code"` // 2FA 开启时必填(TOTP 6 位)
}
```
- 账户开启 2FA(`otp_secret` 非空)时,单次 `POST /api/auth/login` 不带 `otp_code`
直接返回 `{"code":402,"message":"Invalid 2FA code"}`。**是一步登录**,不是两步 challenge。
- 登录成功返回:`{"code":200,"data":{"token":"<JWT>","device_key":"<md5>"}}`。
- `GET /api/admin/user/list` 返回的员工 `password` 字段恒为 `""`(脱敏),`otp` 状态不在此端点,
需 `GET /api/me` 才见 `otp: true/false`。
### 9.2 Role CRUD 实测返回值(修正 §3)
| 端点 | 实测返回 | 备注 |
|---|---|---|
| `role/list` | `{"code":200,"data":{"content":[...],"total":N}}` | `PageResp` 包裹,非顶层数组 |
| `role/create` | `{"code":200,"data":null}` | **不返回新建 role 对象**;需再 `role/list` 按 `name` 取 id |
| `role/update` | `{"code":200,"data":null}` | 成功即 null;`role/get?id=` 可回读确认 |
| `role/get?id=` | `{"code":200,"data":{...}}` | 返回完整 role 含 `permission_scopes` |
| `role/delete` | **`POST /api/admin/role/delete?id=4`(id 走 query param)** | 用 JSON body 传 id 会 `400 strconv.Atoi: parsing "": invalid syntax` |
- `role/delete` 与 `user/delete` **都必须 `?id=` query param**,不是 body。
- `user/create`、`user/delete` 同样实测成功(返回 `data:null`)。
### 9.3 admin 账户(当前实例)
- 用户名 `hoelee`,角色 `admin`(role id 2,`permission 65535`,`permission_scopes` = `[{"/", 65535}]`)。
- `guest` role(id 1)`disabled`,`permission 0`。
- admin 登录曾开 2FA(已由用户关闭);本系统若需 n8n 无人值守,**建议 admin 账户保持 2FA 关闭
或用独立 service token**——否则 n8n 每次登录都要算 TOTP。
### 9.4 网络可达性(关键部署事实)
- AList 容器名 `alist-serve-resource` 仅 Docker **网络内部**可解析。
**DSM host shell 无法解析该名字**(`Could not resolve host`),须用 `localhost:5244`。
- n8n 容器在 `bridge_hoelee` 网络内,访问 AList 用 **`http://alist-serve-resource:5244`**(✅)。
- 旧 `alist` 容器(v3.54.0)现在映射 host 端口 5243,新容器占 5244,无冲突。
### 9.5 ⚠ JWT secret 不一致(待修复,否则 token 会失效)
- 容器 env `JWT_SECRET=<redacted>=`(.env 传入),但 AList 初始化时写入 `data/config.json`
的 `jwt_secret` 是另一个值 `<redacted>`(首次启动自动生成)。
- **env `JWT_SECRET` 只影响首次初始化**;服务运行后以 `config.json` 为准(当前生效的是
`<redacted>`)。两者不一致是正常的(env 在初次 init 时可能被 config 覆盖)。
- 影响:若将来重建数据目录(清空 `data/`),.env 里的 `JWT_SECRET` 会生效;否则一直用 config.json 上的值。
**n8n 不关心这个**(每次 `login` 换 token),但需知悉「改 env 不会改已运行实例的 JWT」。
- 若要统一:改 `data/config.json` 的 `jwt_secret` 并重启,或接受 config.json 为准。
### 9.6 n8n 集成结论(Phase 3 通过 ✅)
- admin 登录 token 可用,Role/User 全 CRUD 可用,scope 读写可用。
- n8n workflow 实现要点(§8)全部成立,唯一补充:**delete 走 `?id=`**,**create/update 返回 null
需回查**(用 `list` 按 `name` 定位 id)。
### 9.7 ⚠ `user/create` 空 role 会失败(Phase 4/W1 debug 实测,源码级确认)
- **症状**:`POST /api/admin/user/create` 传 `"role": []` 时返回
`{"code":400,"message":"admin or guest user can not be created"}`,且与 username 无关(任意名字都失败)。
- **根因**(`server/handles/user.go` `CreateUser`):
1. `if len(req.Role) == 0 { req.Role = model.Roles{op.GetDefaultRoleID()} }` —— 空 role 被自动填为
**默认 role(guest,id=1)**。
2. 紧接着 `if req.IsAdmin() || req.IsGuest() { ... 400 ... }`,其中
`IsGuest()` = `Role.Contains(GUEST)`(`GUEST=1`)、`IsAdmin()` = `Role.Contains(ADMIN)`(`ADMIN=2`)。
role 现在是 `[1]`(guest)→ 判定为 guest → 拒绝。
- **关键认知**:`IsGuest/IsAdmin` 判断的是 **role ID**,不是 username。错误信息里的 "admin or guest
user" 会误导向「用户名保留了」,实际是「role 里含 guest/admin ID」。
- **正确顺序(必须)**:
1. `role/create`(name=`customer-<username>`,空 `permission_scopes`)→ 返回 200 但 `data:null`;
2. `role/list` 按 name 找到新建 role 的自增 id(3、4、5…);
3. `user/create` 传 `"role": [<该 role id>]`(**非空、非 1/2**)→ 成功,`base_path` 正确。
- 实测:`role:[5]` 创建用户 `code 200`,`base_path:/Class`、`permission 16384` 均落地。
- **适用**:W1 的「新建用户」路径必须先建 role;更新路径 `user/update` 里 `role:[]` 同样会触发
`GetDefaultRoleID` 回填(见 `UpdateUser` 同款逻辑),若客户已有 role id 应显式传入。
- 相关常量:`GENERAL=0, GUEST=1, ADMIN=2, NEWGENERAL=3`(`internal/model/user.go`);`permission`
bitmask 定义同文件注释(bit 14 = "check path limit" = 16384)。