13 KiB
13 KiB
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
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 示例:
{
"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
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 示例(只读客户):
{
"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= bitmask0(不设 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)证实:
- 用户 base path 只是登录后的默认落点,不是 ACL(规格 §2/§30)。
- 若用户
CheckPathLimit()为真(bit 14 置位),请求路径必须落在该用户任一 role 的任一 permission scope 的 path 之内(前缀匹配),否则返回PermissionDenied。 - 因此「Alice 只能进 /Class/A/1 与 /Class/A/2,进不了 /Class/A/3 与 /Class/B」
的正确实现 = Alice 专属 role 的
permission_scopes只含这两个 path,且 userpermissionbit 14 置位。
7. 保护性约束(源码确认)
admin/guestrole 受保护: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 结构:
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}])。 guestrole(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.goCreateUser):if len(req.Role) == 0 { req.Role = model.Roles{op.GetDefaultRoleID()} }—— 空 role 被自动填为 默认 role(guest,id=1)。- 紧接着
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」。 - 正确顺序(必须):
role/create(name=customer-<username>,空permission_scopes)→ 返回 200 但data:null;role/list按 name 找到新建 role 的自增 id(3、4、5…);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);permissionbitmask 定义同文件注释(bit 14 = "check path limit" = 16384)。
9.8 ✅ 正确结论:role permission_scopes + base_path="/" = 多文件夹授权(已实测确认)
最终确认的多文件夹授权正确做法(2026-08-22 实测 mama 账号验证通过):
- 组合:
base_path = "/"(根)+ rolepermission_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。必须/。- 运维铁律:W1 的
base_path必须写死'/',不要从 NocoDB 字段读(否则库存/Class旧值会复发)。 已在 W1Prepare Provision Payload硬编码basePath: '/'(commit 1ac1f17)。 - 注意:
CreateUser里有u.BasePath = scopes[0].Path的覆盖逻辑,但仅当 role 有 scope 时触发; 若创建时先建空 role(无 scope),base_path 不会被覆盖。W1 顺序:先建空 role → 建 user (base_path=/)→ W2 再填 scope。实测当 base_path 被显式设为/且 role 后续填 scope 时, 重新 login/访问正常。
- 运维铁律:W1 的
- 不需要 Alias storage(§9.9 曾是备选,现已确认 role scope 路线够用且更简单)。
9.9(备选,未采用)Alias storage —— 一用户看多分散文件夹的另一种方式
(保留作为参考:drivers/alias/driver.go,paths 换行 别名:目标,writable:false 只读。
但既然 role scope + base_path=/ 已能满足,除非有「合并路径语义/去重/隔离」特殊需求,否则不必上 Alias。)