Files
pikasTech-HWLAB/docs/reference/spec-user-access.md
T
2026-06-05 17:23:56 +08:00

184 lines
9.4 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.
# 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`。 |