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

11 KiB
Raw Blame History

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 = 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 结构:

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)。