184 lines
9.4 KiB
Markdown
184 lines
9.4 KiB
Markdown
# v0.2 用户和权限管理规格
|
||
|
||
本文定义 HWLAB `v0.2` 用户、session、API key、Code Agent owner 和工具能力的最小权限口径。当前 HWPOD 快速闭环阶段只设计核心业务功能;不为旧设备路由、旧授权表或旧 REST/job 保留兼容路径。
|
||
|
||
登录入口、Keycloak OIDC、Web session、CLI API key 和 `AuthPrincipal` 归一见 [spec-v02-auth.md](spec-v02-auth.md)。OpenFGA、Admin Access WebUI 和同路径 CLI 细节见 [spec-v02-openfga-authorization.md](spec-v02-openfga-authorization.md)。HWPOD 概念、`hwpod-spec`、`hwpod-cli`、`hwpod-ctl`、`hwpod-compiler-cli`、`hwpod-node-ops` 和 `hwpod-node` 见 [spec-hwpod-harness.md](spec-hwpod-harness.md)。
|
||
|
||
## 在系统中的职责划分
|
||
|
||
用户和权限管理不拆独立微服务,权威实现收敛在 `hwlab-cloud-api`:它消费认证模块产出的 `AuthPrincipal`,负责角色、OpenFGA check/write、用户 API key、tool capability 和 Code Agent session owner 校验。`hwlab-cloud-web` 只提供浏览器 UI 和同源代理;AgentRun v0.1 只消费 cloud-api 按用户权限注入的 actor/session/tool 上下文,不成为 HWLAB 用户权限 authority。
|
||
|
||
Postgres 是用户、session、业务对象和迁移 ledger 的持久化边界;OpenFGA 是细粒度授权关系与授权判定边界。Kubernetes namespace、ServiceAccount、Service 直连和 host route 都不能替代用户权限模型;普通用户不获得 kubeconfig、内部 Service 直连能力、OpenFGA token 或长期 Secret。
|
||
|
||
## 规格目标
|
||
|
||
- 只保留两类基础角色:`admin` 和 `user`。
|
||
- Code Agent session 直接归属于创建它的用户;普通用户只能查看、继续和取消自己的 session。
|
||
- 工具能力独立授权,例如 `hwpod`、`unidesk_ssh`、`trans_cmd` 和 GitHub 写工具;拥有 Code Agent session 不等于拥有这些工具。
|
||
- 当前 HWPOD 快速阶段不引入用户级 hwpod 授权矩阵;`hwpod-spec` 位于 Code Agent workspace,执行链路按 owner session 和工具能力约束。
|
||
- 普通用户不获得 Kubernetes 用户、kubeconfig、namespace 管理权或直接访问内部 Service 的权限;所有用户权限判断在 cloud-api 应用层完成。
|
||
- MVP 不新增产品级 `audit_events` 用户审计表,也不把用户权限依赖到 audit。
|
||
- v0.2 权限数据必须与 `hwlab-dev`/`hwlab-prod` 运行数据隔离。
|
||
|
||
## 表结构
|
||
|
||
### `users`
|
||
|
||
用户身份和角色 source of truth。
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS users (
|
||
id TEXT PRIMARY KEY,
|
||
username TEXT NOT NULL UNIQUE,
|
||
display_name TEXT NOT NULL DEFAULT '',
|
||
role TEXT NOT NULL CHECK (role IN ('admin', 'user')),
|
||
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
|
||
password_hash TEXT,
|
||
created_at TEXT NOT NULL,
|
||
updated_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
### `user_sessions`
|
||
|
||
浏览器 server session/cookie 的持久状态。cookie 中只保存不可逆 session token;数据库保存 token hash。
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS user_sessions (
|
||
id TEXT PRIMARY KEY,
|
||
user_id TEXT NOT NULL REFERENCES users(id),
|
||
session_token_hash TEXT NOT NULL UNIQUE,
|
||
created_at TEXT NOT NULL,
|
||
last_seen_at TEXT NOT NULL,
|
||
expires_at TEXT NOT NULL,
|
||
revoked_at TEXT
|
||
);
|
||
```
|
||
|
||
### `api_keys`
|
||
|
||
CLI 和 runner 的用户 API key 必须映射到 `users.id`。API key 只能收窄用户权限,不能授予超过用户和 OpenFGA tuple 的能力。
|
||
|
||
### `agent_sessions`
|
||
|
||
复用现有 `agent_sessions` 作为 Code Agent session 归属记录,不新增同义表。
|
||
|
||
```sql
|
||
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS owner_user_id TEXT REFERENCES users(id);
|
||
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS conversation_id TEXT;
|
||
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS thread_id TEXT;
|
||
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS last_trace_id TEXT;
|
||
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS session_json TEXT NOT NULL DEFAULT '{}';
|
||
ALTER TABLE agent_sessions ADD COLUMN IF NOT EXISTS updated_at TEXT;
|
||
CREATE INDEX IF NOT EXISTS idx_agent_sessions_owner ON agent_sessions(owner_user_id);
|
||
CREATE INDEX IF NOT EXISTS idx_agent_sessions_conversation ON agent_sessions(conversation_id);
|
||
```
|
||
|
||
新建 Code Agent session 必须写入 `owner_user_id`。历史 ownerless session 在迁移时可以一次性归属 bootstrap `admin` 或标记为 `expired`;迁移完成后不再允许 ownerless 活跃 session。
|
||
|
||
## 权限矩阵
|
||
|
||
| 操作 | `admin` | `user` |
|
||
| --- | --- | --- |
|
||
| 管理用户和权限 | 可以;应镜像为 `system:hwlab#admin` 或 `access_manager` | 只有被授予 `access_manager` 才可以 |
|
||
| 创建自己的 Code Agent session | 可以 | 可以 |
|
||
| 查看、继续、取消自己的 Code Agent session | 可以 | 拥有该 session 的 `viewer/operator` 时可以 |
|
||
| 查看、取消别人的 Code Agent session | 可以 | 需要该 session 的显式 relation |
|
||
| 调用 `hwpod-cli` / `hwpod-ctl` | 可以,但仍受 session owner 和工具边界约束 | 需要 `tool:hwpod#can_use` |
|
||
| 调用 UniDesk SSH / trans cmd / GitHub 写工具 | 可以,但仍受工具边界约束 | 需要对应 `tool:*#can_use` |
|
||
|
||
## 请求链路
|
||
|
||
cloud-api 每个用户态请求都按同一顺序处理:
|
||
|
||
```text
|
||
authenticate -> actor -> authorize(actor, action, resource) -> execute
|
||
```
|
||
|
||
### 登录、API key 和 session 恢复
|
||
|
||
```text
|
||
browser
|
||
-> Keycloak OIDC login/callback
|
||
-> cloud-api auth controller
|
||
-> users issuer+sub/status/role mapping
|
||
-> user_sessions insert token hash
|
||
-> Set-Cookie httpOnly sameSite
|
||
-> browser
|
||
|
||
cli
|
||
-> Authorization: Bearer hwl_live_...
|
||
-> cloud-api api_keys lookup
|
||
-> users status/role check
|
||
-> AuthPrincipal(authMethod='api-key')
|
||
```
|
||
|
||
### 用户创建或继续 Code Agent session
|
||
|
||
```text
|
||
browser
|
||
-> cloud-api POST /v1/agent/chat
|
||
-> authenticate actor
|
||
-> find or create agent_sessions(owner_user_id=actor.id, conversation_id)
|
||
-> run Code Agent turn
|
||
-> persist last_trace_id/thread_id/session_json
|
||
-> return 202 trace/result polling pointer
|
||
```
|
||
|
||
`GET /v1/agent/chat/result/{traceId}`、`GET /v1/agent/chat/trace/{traceId}` 和 `POST /v1/agent/chat/cancel` 必须通过 `agent_sessions.owner_user_id` 校验 owner;`admin` 可跨用户查看和取消。
|
||
|
||
### Code Agent 使用 HWPOD
|
||
|
||
```text
|
||
code agent turn
|
||
-> hwpod-cli in workspace
|
||
-> hwpod-compiler-cli reads .hwlab/hwpod-spec.yaml
|
||
-> hwpod-node-ops plan
|
||
-> cloud-api /v1/hwpod-node-ops
|
||
-> hwpod-node
|
||
```
|
||
|
||
Cloud API 给 AgentRun runner 注入 `hwpod`、UniDesk SSH、`trans_cmd` 或 GitHub 写工具前,必须先按 [spec-v02-openfga-authorization.md](spec-v02-openfga-authorization.md) 检查对应 `tool:*#can_use`。
|
||
|
||
## API 接口说明
|
||
|
||
| 接口 | 说明 |
|
||
| --- | --- |
|
||
| `GET /auth/oidc/login`、`GET /auth/oidc/callback` | Web 登录入口;按 [spec-v02-auth.md](spec-v02-auth.md) 接入 Keycloak 并写入 24 小时 `user_sessions` token hash。 |
|
||
| `GET /auth/session` | 从 cookie 恢复 actor、role 和 session 状态;API key actor 摘要见 `/v1/users/me`。 |
|
||
| `GET /v1/auth/session`、`GET /v1/users/me`、`GET /v1/access/status`、`GET /v1/setup/status` | REST 状态入口;不得读取或返回 password hash、session token 原文或 Secret 值。 |
|
||
| `GET/POST /v1/api-keys...` | 用户 API key 管理入口;CLI `HWLAB_API_KEY` 规则见 [spec-v02-auth.md](spec-v02-auth.md)。 |
|
||
| `POST /v1/setup/first-admin` | 仅当 `users` 表为空时创建第一个 `admin` 并建立 session;一旦已有用户必须返回 `409 setup_already_completed`。 |
|
||
| `POST /auth/logout` | 设置 `revoked_at`,撤销当前 browser session。 |
|
||
| `POST /v1/admin/users` | admin 创建用户,响应不得返回 `password_hash` 或 token。 |
|
||
| `GET/PATCH/PUT/DELETE /v1/admin/access...` | admin Access API,读写 OpenFGA 授权、tool capability、role/status 和 effective matrix。 |
|
||
| `POST /v1/hwpod-node-ops` | HWPOD node-ops 转发入口;业务翻译必须已由 workspace-local compiler 完成。 |
|
||
| `POST /v1/agent/chat` 及 result/trace/cancel | 必须校验 `agent_sessions.owner_user_id`;admin 可跨用户查看和取消。 |
|
||
|
||
## 测试规格
|
||
|
||
## T1
|
||
|
||
阅读 docs/reference/spec-user-access.md,然后用 CLI 手动测试以下内容:创建普通用户,确认 `role=user`、`status=active`,响应不包含 password hash、session token 或 Secret 值。
|
||
|
||
## T2
|
||
|
||
阅读 docs/reference/spec-user-access.md,然后用普通用户创建 Code Agent session,确认 `agent_sessions.owner_user_id` 写入当前用户;另一个普通用户不能读取或取消该 session。
|
||
|
||
## T3
|
||
|
||
阅读 docs/reference/spec-user-access.md,然后撤销普通用户的 `tool:hwpod` 能力,创建新的 Code Agent session 并调用 HWPOD,确认 cloud-api 或 runner 装配返回结构化 authorization blocker。
|
||
|
||
## T4
|
||
|
||
阅读 docs/reference/spec-user-access.md,然后检查 source schema、migration 和 live Postgres,确认不存在旧设备授权表或旧 profile authority 表作为授权来源。
|
||
|
||
## 规格的实现情况
|
||
|
||
| 规格项 | 状态 | 说明 |
|
||
| --- | --- | --- |
|
||
| admin/user 两角色模型 | 已实现/持续约束 | cloud-api 已实现 `/auth/*`、bootstrap admin、admin/user 创建和 admin-only route。 |
|
||
| `users`、`user_sessions`、`api_keys`、OpenFGA relation | 已实现/持续约束 | 当前授权只表达用户、session 和 tool capability;不保留旧设备授权 source。 |
|
||
| Code Agent owner 绑定 | 已实现/持续约束 | session owner、trace/result/cancel 必须按 owner 校验。 |
|
||
| HWPOD 工具能力授权 | 目标状态 | `tool:hwpod` 控制 runner 是否注入 HWPOD 能力;具体 HWPOD 执行链路见 `spec-hwpod-harness.md`。 |
|