# 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`。 |