Files
pikasTech-HWLAB/docs/reference/spec-v02-auth.md
T
2026-06-04 01:06:36 +08:00

250 lines
16 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` 登录、认证和应用层鉴权入口的长期规格。它把 Keycloak OIDC、浏览器 Web session、CLI/API/AgentRun 共用的用户 API key 和 HWLAB 内部 `AuthPrincipal` 归一到同一套口径,避免把本地账号密码、浏览器 cookie 和设备专用系统 key 混用。
基础设施实施跟踪见 [pikasTech/HWLAB#788](https://github.com/pikasTech/HWLAB/issues/788)。用户角色、Code Agent session owner、device pod grant 和资源授权矩阵见 [spec-user-access.md](spec-user-access.md);本文只定义“如何登录、如何恢复 actor、如何把请求归一成 actor”。
## 设计目标
- Web 用户通过 Keycloak OIDC 登录或注册,HWLAB 不再把本地账号密码表单作为目标登录体验。
- Web 使用 `hwlab-cloud-api` 发行的 httpOnly `hwlab_session`,每个 session token 最长 24 小时;第一版不做复杂 refresh token 管理,但必须可撤销、可重新登录轮换。
- CLI 是纯 CLI 体验,不打开浏览器、不跳转 Web、不做 device-code flow;标准凭据是环境变量 `HWLAB_API_KEY`
- AgentRun runner 内的 `hwpod` 也必须使用同一类用户 API key 认证,映射到发起 Code Agent session 的 `users.id`;不再引入单独的 `HWLAB_DEVICE_POD_API_KEY`
- 每个用户在首次登录后自动拥有一个默认 API key;用户也可以在 Web 中创建、查看、失效或重新生成 API key。
- API key 长效有效,除非用户或管理员手动 revoke/regenerate;它不跟 Web session 的 24 小时过期绑定。
- Keycloak 只做身份认证和账号注册;HWLAB 的 `users.role``users.status`、device pod grant 和 Code Agent owner 仍是应用层授权 source of truth。
- device-pod 权限只由 `users.role/status`、Code Agent session owner 和 `device_pod_grants` 控制;不存在“对所有正式 device pod 授权”的共享 runner key。
## 系统边界
| 组件 | 职责 |
| --- | --- |
| Keycloak | 独立 `keycloak` namespace 中的身份提供方,提供公网 HTTPS OIDC issuer、登录页、注册页和用户 subject。 |
| Keycloak Postgres | Keycloak 专用数据库,短期可与 Keycloak 同在 `keycloak` namespace;不复用 HWLAB v0.2 业务 Postgres。 |
| `hwlab-cloud-api` | OIDC callback、session token、API key、用户映射、`AuthPrincipal` 和应用层授权收口。 |
| `hwlab-cloud-web` | 浏览器 UI 和同源代理;未登录时引导到 Keycloak,不持有 Keycloak client secret,不直接访问 Postgres。 |
| `hwlab-cli` / AgentRun `hwpod` | 读取 `HWLAB_API_KEY`,向 Cloud Web 同源 API 或 Cloud API 发送 `Authorization: Bearer hwl_live_...`,恢复到同一个用户 actor。 |
| `hwlab-edge-proxy` / FRP | 只转发 cookie/header 和 HTTP 流量,不注入伪 actor,不做业务授权。 |
| HWLAB v0.2 Postgres | 保存 HWLAB 用户映射、session、API key、resource grant 和 runtime durable state。 |
Keycloak 部署不进入 `hwlab-v02` namespace。`hwlab-v02` 只把 Keycloak 当成外部 OIDC issuer 消费,使用 issuer URL、client id 和 client secret SecretRef 完成 callback 校验。
## 公网 HTTPS 与 issuer
Keycloak 必须有稳定公网 HTTPS issuerOIDC redirect URI、cookie secure policy 和浏览器登录都依赖这一点。短期全自动方案使用主 server Caddy + Let's Encrypt + nip.io 域名,并通过 FRP 把 G14 `keycloak` namespace 的 Keycloak Service 暴露到主 server 本地端口。
目标 issuer
```text
https://auth.74-48-78-17.nip.io/realms/hwlab
```
约束:
- Keycloak admin console、account console、OIDC discovery 和 JWKS 都必须走 HTTPS;不要用裸 HTTP issuer。
- `hwlab-cloud-api` 只记录 issuer、client id、redirect URI 和 SecretRef 名称,不在日志、issue 或 ConfigMap 中输出 client secret。
- Keycloak 的 `hostname`、proxy header 和 frontend URL 必须与公网 issuer 一致,避免 callback 后 token issuer mismatch。
- `hwlab-cloud-api``hwlab-cloud-web` 和 CLI 仍保持 v0.2 原入口 `19666/19667`Keycloak 只新增 auth 域名,不替代 HWLAB Cloud Web。
## 注册策略
短期小范围测试允许 Keycloak 开启自助注册,并且不要求邮箱或手机校验。该策略只有在以下条件同时满足时成立:
- 新注册用户默认只映射为 HWLAB `user` 角色,不自动成为 `admin`
- 新用户没有 device pod grant,也不能看到或使用任何 device pod,直到 `admin` 在 HWLAB 中授权。
- Keycloak 账号状态必须能被管理员禁用;HWLAB `users.status='disabled'` 也必须能独立阻断 session 和 API key。
- 如果公网注册出现垃圾账号或撞库迹象,第一优先级是关闭 Keycloak self-registration 或增加邀请码/管理员审核;不要把防滥用逻辑塞进 device pod 授权。
邮箱和手机字段可以作为 profile 信息保存,但第一版不要求验证。后续如果进入更大范围公网使用,应至少补充邮箱验证、注册限流或邀请制中的一种。
## 身份映射
Keycloak subject 不直接当业务授权 ID。`hwlab-cloud-api` 在 OIDC callback 中按 `issuer + sub` 查找或创建 HWLAB `users` 记录,然后所有业务授权都使用 `users.id`
推荐扩展 `users`
```sql
ALTER TABLE users ADD COLUMN IF NOT EXISTS auth_provider TEXT NOT NULL DEFAULT 'local';
ALTER TABLE users ADD COLUMN IF NOT EXISTS keycloak_issuer TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS keycloak_sub TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS email TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TEXT;
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_keycloak_subject
ON users(keycloak_issuer, keycloak_sub)
WHERE keycloak_issuer IS NOT NULL AND keycloak_sub IS NOT NULL;
```
映射规则:
- 首次 OIDC 登录:按 `issuer + sub` 不存在则创建 `users`,默认 `role='user'``status='active'`
- 已存在本地 bootstrap admin 时,可以由 admin 在 HWLAB 内手动绑定 Keycloak subject 或提升 Keycloak 用户为 `admin`;Keycloak 注册本身不得自动提权。
- Keycloak profile 更新只同步 `username/display_name/email` 这类展示信息,不覆盖 HWLAB `role/status`
- `disabled` 用户不能创建 Web session,不能通过 API key 鉴权,也不能继续 Code Agent session。
## AuthPrincipal
所有用户态请求最终归一成 `AuthPrincipal`。业务模块只消费 `AuthPrincipal`,不直接判断 Keycloak token、cookie 或 API key 原文。
```ts
type AuthPrincipal = {
userId: string;
username: string;
displayName: string;
role: "admin" | "user";
status: "active" | "disabled";
authMethod: "web-session" | "api-key" | "legacy-local-session";
sessionId?: string;
apiKeyId?: string;
keycloakIssuer?: string;
keycloakSub?: string;
scopes: string[];
};
```
认证顺序必须显式区分两类正式用户凭据和 legacy fallback
1. `Authorization: Bearer hwl_live_...``HWLAB_API_KEY` 映射出的 header 是用户 API key,适用于 CLI 和 AgentRun runner 内 `hwpod`
2. `hwlab_session` cookie 是 Web session。
3. 本地账号密码 session token 只作为 legacy/debug fallback。
现有 `Authorization: Bearer <session-token>` 兼容入口只能保留为 legacy/debug,不得阻塞 `hwl_live_` 前缀的用户 API key 识别。
## Web 登录流程
```text
browser -> Cloud Web /
-> no valid hwlab_session
-> GET /auth/oidc/login?returnTo=/
-> 302 Keycloak login/register
-> Keycloak callback with code
-> hwlab-cloud-api validates OIDC code/issuer/JWKS
-> upsert users by issuer+sub
-> ensure default API key exists for the user
-> create user_sessions token hash, expires_at <= now + 24h
-> Set-Cookie hwlab_session=httpOnly; Secure; SameSite=Lax
-> redirect returnTo
```
Web 页面不要求用户输入或复制 API key。浏览器请求通过 `hwlab_session` 恢复 `AuthPrincipal`;需要 CLI 时,用户在 Web 的 API key 页面查看或重新生成自己的 key。
Session 规则:
- 每次 OIDC 登录或 callback 成功都发行新的 session token,数据库只保存 token hash。
- 单个 session token 最长 24 小时;过期、用户 disabled 或 logout 后必须返回 `401 auth_session_invalid`
- `/auth/logout` 撤销 HWLAB session,并可选择跳转 Keycloak logout;即使 Keycloak 全局会话仍有效,HWLAB 已撤销的 session 也不能继续使用。
- 第一版不实现长期 refresh token 存储;用户过期后重新走 Keycloak 登录。
## CLI API key 流程
CLI 和 AgentRun `hwpod` 标准认证方式:
```bash
export HWLAB_API_KEY=hwl_live_xxx
bun tools/hwlab-cli/bin/hwlab-cli.ts client auth whoami
```
约束:
- CLI 不跳转浏览器,不依赖 Web cookie,不要求 username/password 交互。
- `hwlab-cli` 默认从 `HWLAB_API_KEY` 读取 key,并发送 `Authorization: Bearer <key>`
- AgentRun runner 的 transient env 只允许注入映射到当前 Code Agent session owner 的 `HWLAB_API_KEY`;可以使用该用户默认 key,也可以使用同一 `api_keys` 表中为该用户创建的 runner 专用 key,但绝不能使用跨用户共享的 device-pod 系统 key。
- `client auth status` 必须显示 endpoint、是否检测到 `HWLAB_API_KEY`、key prefix 和用户摘要;不得输出完整 API key。
- `client auth whoami``client request GET /v1/users/me` 必须能用 API key 返回与 Web 同一用户的 `AuthPrincipal` 摘要。
- `client auth login --username ...` 和本地 cookie profile 属于 legacy 兼容入口;目标体验不再把它作为一等 CLI 登录。
- `hwpod` 在 runner 中必须自动使用 `HWLAB_RUNTIME_API_URL``HWLAB_API_KEY` 直连 `hwlab-cloud-api`,并让 cloud-api 按该 key 对应用户的 role/grant 做授权。
API key 数据结构:
```sql
CREATE TABLE IF NOT EXISTS api_keys (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name TEXT NOT NULL DEFAULT 'Default API key',
key_prefix TEXT NOT NULL,
key_hash TEXT,
display_secret TEXT,
scopes_json TEXT NOT NULL DEFAULT '[]',
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
created_at TEXT NOT NULL,
last_used_at TEXT,
revoked_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys(user_id, status, created_at DESC);
CREATE UNIQUE INDEX IF NOT EXISTS idx_api_keys_prefix ON api_keys(key_prefix);
```
短期小范围测试允许 `display_secret` 保存可重复展示的 API key 明文,满足“用户不必只在生成时保存一次”的体验;但完整 key 不得写入日志、issue、ConfigMap、Kubernetes Secret、trace 或 CLI 默认输出。生产化时应删除 `display_secret`,改成只保存 `key_hash`,并只在生成时显示一次。
API key 行为:
- 首次 OIDC 登录后自动创建默认 key;如果默认 key 被 revoke,用户可在 Web 中重新生成。
- 默认 key、手动创建的 key 和 AgentRun runner 使用的 key 都继承该用户的 `role/status/grant`,不额外绕过权限矩阵。
- revoke 后同一 key 立刻返回 `401 api_key_invalid`,不能等 Web session 过期。
- 每次 API key 成功认证应更新 `last_used_at`,但不得记录完整 key。
## API 接口
| 接口 | 说明 |
| --- | --- |
| `GET /auth/oidc/login?returnTo=...` | 发起 Keycloak OIDC 登录,保存 state/nonce 并 302 到 Keycloak。 |
| `GET /auth/oidc/callback` | 校验 code、issuer、nonce 和 state,映射用户,发行 24 小时 Web session。 |
| `GET /auth/session` | 从 Web cookie 或兼容 session 恢复 actorAPI key 请求可返回 `authMethod='api-key'` 摘要。 |
| `POST /auth/logout` | revoke 当前 Web session 并清 cookie。 |
| `POST /auth/login` | 本地账号密码 legacy/bootstrap fallback;目标 Web/CLI 登录不依赖该入口。 |
| `GET /v1/users/me` | 返回当前 `AuthPrincipal` 的脱敏摘要。 |
| `GET /v1/api-keys` | 列出当前用户 API key metadata;短期测试可返回默认 key 的 displayable 状态。 |
| `GET /v1/api-keys/default` | 返回或创建默认 API key;短期测试允许返回完整 key 明文给当前用户本人。 |
| `POST /v1/api-keys` | 创建一个用户 API key。 |
| `POST /v1/api-keys/{id}/regenerate` | 重新生成指定 API key,旧 secret 立即失效。 |
| `DELETE /v1/api-keys/{id}` | revoke 指定 API key。 |
所有 API key 管理接口都要求当前 `AuthPrincipal` 是同一用户本人或 `admin`;普通用户不能查看或生成其他人的 key。响应默认只返回 `key_prefix``id``name``status``last_used_at` 和短期测试允许的本人完整 key。
## 与资源授权的关系
登录和认证只回答“请求是谁”。资源授权仍由 [spec-user-access.md](spec-user-access.md) 定义:
- `admin` 可以管理用户、device pod profile/grant,并跨用户查看或取消 Code Agent session。
- `user` 只能访问自己的 Code Agent session 和被授权的 device pod。
- Web session、CLI API key 和 AgentRun runner 内 `hwpod` API key 得到同一个 `users.id` 时,应看到相同 device pod grant 和账号 workspace。
- Keycloak realm role、group 或 claim 不直接决定 HWLAB device pod 权限;最多作为创建/绑定用户时的输入线索。
## 测试规格
## T1
阅读 docs/reference/spec-v02-auth.md,然后用 CLI 或 curl 验证 `https://auth.74-48-78-17.nip.io/realms/hwlab/.well-known/openid-configuration` 可访问,issuer 与配置完全一致,JWKS URL 为 HTTPS,响应不包含 admin password、client secret 或数据库连接串。
## T2
阅读 docs/reference/spec-v02-auth.md,然后从浏览器访问 `http://74.48.78.17:19666/`。未登录时必须进入 Keycloak 登录/注册;注册新用户不要求邮箱或手机验证,callback 后回到 Workbench`GET /auth/session` 返回 active `user` actor 和 24 小时内过期的 session,不返回 session token 原文。
## T3
阅读 docs/reference/spec-v02-auth.md,然后登录 Web 后打开 API key 管理入口,确认默认 API key 已自动存在;短期测试中可重复查看完整 key。用 `HWLAB_API_KEY=<key> bun tools/hwlab-cli/bin/hwlab-cli.ts client auth whoami` 验证 CLI 无浏览器跳转即可恢复同一用户。
## T4
阅读 docs/reference/spec-v02-auth.md,然后 revoke 或 regenerate API key,再用旧 `HWLAB_API_KEY` 请求 `/v1/users/me``/v1/device-pods`,必须返回 `401 api_key_invalid`;同一用户重新登录 Web session 不应恢复旧 key。
## T5
阅读 docs/reference/spec-v02-auth.md,然后分别使用 Web session 和同一用户的 CLI API key 访问 `/v1/device-pods`、创建 Code Agent session 和读取自己的 trace/result,确认授权结果一致;另一个普通用户的 API key 不能读取该 sessionadmin 可以跨用户查看。
## T6
阅读 docs/reference/spec-v02-auth.md,然后检查 cloud-api 日志、trace、CLI 默认输出和 issue 复现材料,确认不出现 Keycloak client secret、session token、完整 API key、历史 `HWLAB_DEVICE_POD_API_KEY` 或 Postgres URL 原文;AgentRun runner 中也不得存在跨用户共享的 device-pod 系统 key。
## 规格的实现情况
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| Keycloak 独立 namespace 与公网 HTTPS issuer | 目标状态 | 实施跟踪见 #788;短期建议 `auth.74-48-78-17.nip.io` + Caddy/Let's Encrypt + FRP。 |
| Keycloak 自助注册且不强制邮箱/手机验证 | 目标状态 | 只适合小范围测试;默认 `user`、无 device pod grant。 |
| Web OIDC login/callback | 目标状态 | 当前源码仍以 `/auth/login` 本地账号密码为主。 |
| Web session 24 小时轮换 | 目标状态 | 当前 `internal/cloud/access-control.ts` 为 7 天本地 session,后续需改为 24 小时。 |
| CLI/AgentRun `HWLAB_API_KEY` 一等登录 | 目标状态 | 当前 CLI 主要保存 cookie sessionAgentRun/device-pod 仍有旧 shared key 口径;目标是统一 env API key 映射到用户,无浏览器跳转,无跨用户 device-pod key。 |
| 默认 API key 自动生成和 Web 可查看 | 目标状态 | 小范围测试允许重复查看明文;生产化再改为 hash-only。 |
| `AuthPrincipal` 归一 | 目标状态 | 后续实现必须把 Web session、CLI API key 和 AgentRun `hwpod` API key 都归一成同一用户 actor。 |
| `admin/user` 与 device pod grant 授权 | 部分实现 | 现有 cloud-api 已有本地用户、session、grant 和 Code Agent owner 绑定;资源授权继续按 spec-user-access 收敛。 |