# Scert 项目成员接口交接 > 本文件保留成员接口批次契约。新增可见项目入口和成员页面的当前交接见同目录 `api-handoff-v2.md`;下方“不提供项目列表或页面”描述仅对应原批次。 ## 业务范围与交付边界 项目成员关系用于管理同一项目内的六类角色,与用户分组独立。普通用户可以担任项目管理员,普通全局管理员不因登录角色或同组身份自动取得项目权限。接口允许当前有效超级管理员或目标项目的项目管理员管理成员。 本批提供成员列表、授予与撤销接口,不提供项目列表/创建入口或成员页面,也不代表域名、证书、服务器、DNS 等全部业务资源已经接入项目授权。源码专项、实库与构建证据分别记录于《构建版本与数据库支持矩阵》第二十三节,不据此声明生产验收通过。 ## 通用约定 - 前缀:`/api/v1/projects/:projectID/members`,路径必须使用实际项目标识。 - 使用正式登录接口建立的会话 Cookie;浏览器请求同源发送。服务器验证提供的 `Origin`/`Referer`,来源拒绝返回 403。 - 不传 `actor_id`、全局角色或项目权限;操作者仅取本次服务端验证的会话,仓储执行时重新核对账户状态和成员关系。 - 项目、用户及游标输入仅允许 1~128 位 ASCII 字母、数字、连字符、下划线;空游标例外。已有项目实体本身仍遵守其更严格的命名规则,此接口不创建项目。 - 响应头 `X-Scert-Request-ID` 可用于关联诊断。错误响应为 `{"error":"中文提示","request_id":"请求编号"}`,不能仅按文本匹配决定权限。 - 成功授权/撤销和成功审计在同一事务提交;失败时不得乐观更新界面。成功变更后应重新获取第一页,清空旧游标栈。 ## 接口 ### GET:读取成员列表 ```http GET /api/v1/projects/default/members?page_size=10&after=user-001 ``` | 查询参数 | 类型 | 约束 | |---|---|---| | `page_size` | 整数 | 默认 10;只接受 5、10、15、30 | | `after` | 字符串 | 默认空,从第一页读取;下一页使用上一响应的 `next_cursor` | 重复参数、未知参数、非法转义、无效页大小或游标返回 400。没有页码或总人数参数。 成功返回 200: ```json { "items": [ { "user_id": "user-002", "username": "测试成员", "status": "active", "roles": ["dns_admin", "read_only"] } ], "page_size": 10, "has_more": false, "next_cursor": "" } ``` - 按用户 ID 升序的游标分页,不按角色关系计数,同一账户的角色合并到一个成员项中。 - `items` 始终为数组;合法空项目和末尾空页为 `[]`,不是未授权。`has_more=false` 时 `next_cursor` 为空。 - 列表包含仍有成员关系的停用账户,便于撤销其角色;停用账户自身无法操作接口。 - 不返回邮箱、密码摘要、用户分组或全局角色。角色顺序由接口稳定排序,界面应按角色值映射中文名称。 - 超级管理员不隐式出现在成员列表中;只有显式成员关系才列出。空默认项目不会自动增加成员。 - 游标不是快照。翻页期间发生授权变更时,应允许用户刷新并回到第一页,不承诺跨请求一致性。 ### POST:授予角色 ```http POST /api/v1/projects/default/members Content-Type: application/json ``` ```json { "user_id": "user-002", "role": "dns_admin" } ``` - 目标必须为真实有效账户,不能凭用户名或邮箱替代 `user_id`。 - 正文只接受 `user_id`、`role`,拒绝未知字段、多段 JSON 和超过 64 KiB 的正文;不接受查询参数。 - 成功为 201:`{"message":"项目角色已授予"}`。 - 已存在相同项目/账户/角色关系返回 409,不生成重复关系;其他角色可另行授予。 - 同一账户可在多个项目持有多个角色,但该规则不改变普通用户只能属于一个用户分组的限制。 ### DELETE:撤销角色 ```http DELETE /api/v1/projects/default/members/user-002/roles/dns_admin ``` - 不接受请求体或查询参数,空白请求体也拒绝。不能使用 DELETE 正文冒充操作者。 - 成功为 204,无响应体;客户端不要尝试解析成功响应 JSON。 - 不存在的目标关系与权限不足均返回 403,不作为幂等成功。 - 允许项目管理员撤销自己的项目管理员角色。成功后应退出成员管理状态或处理重新读取产生的 403,不能重试为自己恢复权限;有效超级管理员仍可恢复授权。 ## 角色枚举与显示名称 | 值 | 中文名称 | |---|---| | `project_admin` | 项目管理员 | | `certificate_admin` | 证书管理员 | | `deployment_admin` | 部署管理员 | | `dns_admin` | DNS 管理员 | | `audit_viewer` | 审计查看者 | | `read_only` | 只读用户 | `super_admin` 不是可授予的项目角色。角色能力矩阵见《项目资源关联模型》9.1 节;不能因为名称相近就授予未接入资源的权限。普通账户不能读取完整私钥的上限不因项目管理员角色而解除。 ## 错误与审计边界 | HTTP 状态 | 含义与界面处理 | |---|---| | 400 | 输入无效;显示中文提示,不重试相同请求 | | 401 | 未登录、会话失效或账户已停用;重新认证 | | 403 | 来源拒绝、无项目权限、项目不存在、目标账户/关系不满足条件;不区分这些情况枚举他人资源 | | 409 | 项目成员关系已存在,或系统尚未初始化;以当前流程和错误响应区分,不当作成功 | | 500 | 操作或事务审计失败;不展示底层数据库错误,不乐观认为授权已生效 | | 503 | 成员服务未接入或不可用;禁止将空白页面解释为没有成员 | 会话验证后、仓储执行前发生撤权或停用仍会拒绝。失败审计保留本次已验证身份;未认证请求不归到客户端声明的账户。失败审计独立于回滚事务,记录固定动作、HTTP 状态、目标及请求编号,不保存请求正文或任意底层错误。请求取消后审计仍有独立的三秒上限;审计存储自身失败时只写稳定服务器诊断日志,原失败响应保持不变,不能承诺事件已持久化。 ## 前端集成与验收场景 1. 按账户分页并显示中文角色标签;用户可选择 5、10、15、30 条,切换页大小回到第一页。 2. 保存各页起始游标用于上一页;不将 `next_cursor` 当作页码,不伪造总人数。 3. 授权提交后等待 201,再刷新列表;重复授权 409 显示冲突并刷新,不生成本地重复项。 4. 撤销前确认目标与角色,自我撤权单独提示;204 后不解析 JSON,失去权限时停止继续操作。 5. 分别验证普通全局管理员、只读项目成员、其他项目管理员均无法管理本项目成员。 6. 测试空列表、末页、停用成员、多角色账户和翻页期间关系变更;不显示邮箱或其他未返回字段。 7. 401/403/500/503 分别显示正确状态,不能统一伪装为空列表;诊断信息展示请求编号而非数据库原文。 ## 待完成 - [ ] 项目列表/创建及入口选择、授权目标账户选择、成员页面与浏览器验收。 - [ ] 全部业务资源与排队任务执行/取消/重试的权限重判。 - [ ] PostgreSQL 当前实库、Linux 原生及完整生产部署验收;已通过的局部测试不替代这些门禁。