From 37bd7ef917816aae5c6c22a4337dfdde0bb4c1d1 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Tue, 14 Apr 2026 16:32:15 +0800 Subject: [PATCH] docs: expand bilingual engineering references --- docs/api/auth.md | 532 ++++++++++++++++++++++-- docs/api/endpoints.md | 179 ++++++-- docs/api/errors.md | 360 +++++++++++++++- docs/api/overview.md | 220 +++++++++- docs/architecture/components.md | 115 ++++- docs/architecture/design-decisions.md | 53 ++- docs/architecture/overview.md | 159 +++++-- docs/development/code-structure.md | 576 +++++++++++++++++++++++++- docs/development/testing.md | 90 +++- docs/en/README.md | 52 ++- docs/en/architecture.md | 48 ++- docs/en/design.md | 38 +- docs/en/developer-guide.md | 55 ++- docs/zh/README.md | 54 ++- docs/zh/architecture.md | 48 ++- docs/zh/design.md | 38 +- docs/zh/developer-guide.md | 55 ++- 17 files changed, 2313 insertions(+), 359 deletions(-) diff --git a/docs/api/auth.md b/docs/api/auth.md index 579ea8f..e3b69c1 100644 --- a/docs/api/auth.md +++ b/docs/api/auth.md @@ -1,49 +1,513 @@ -# 认证与鉴权 +# Authentication And Authorization / 认证与鉴权 -## 会话认证(默认) +## 中文 -1) 登录 `POST /api/auth/login` 成功后返回: -- `token`:会话 token -- `expiresAt` -- `user` +### 当前认证模型 -2) 客户端后续请求携带: -- `Authorization: Bearer ` 或 -- Cookie `xc_session=` +`accounts.svc.plus` 不是“纯 JWT API”,而是: -## XWorkmate Vault 集成 +- 以 session token 为主控制面事实来源。 +- 以 `xc_session` cookie 和 `Authorization: Bearer ` 为主调用方式。 +- 在 `auth.enable` 打开时,为部分路由叠加 JWT middleware。 +- 对 `/api/internal/*` 使用 internal service token。 +- 对 `/api/agent-server/v1/users|status` 使用 agent token。 -- `GET /api/auth/xworkmate/profile` 继续只返回非敏感配置、locator 元数据和 `tokenConfigured` -- `PUT /api/auth/xworkmate/profile` 禁止持久化任何 raw token/password/api key 字段 -- `GET /api/auth/xworkmate/secrets` 只返回 target / locator / configured|missing 状态 -- `PUT /api/auth/xworkmate/secrets/:target` 与 `DELETE /api/auth/xworkmate/secrets/:target` 走服务端 Vault backend -- 所有 XWorkmate secret API 都不会返回 raw secret +这意味着“JWT 已启用”并不等于“业务只靠 JWT 运行”;很多 handler 仍会继续读取 session。 -## 邮件验证 +### 认证方式总表 -- 发送验证码:`POST /api/auth/register/send` -- 验证并注册:`POST /api/auth/register/verify` +| 方式 | 典型接口 | 传入位置 | 成功后得到什么 | +| --- | --- | --- | --- | +| Session token | `/api/auth/login` `/api/auth/session` `/api/auth/xworkmate/*` | `Authorization` 或 `xc_session` cookie | 当前用户上下文、管理员权限、XWorkmate profile 读写能力。 | +| JWT refresh | `POST /api/auth/token/refresh` | JSON body `refresh_token` | 新 `access_token`。 | +| OAuth exchange code | `POST /api/auth/token/exchange` | JSON body `exchange_code` | 真实 session token,字段名同时以 `token` / `access_token` 返回。 | +| Internal service token | `/api/internal/*` | `Authorization: Bearer ` | 受信任服务读接口。 | +| Agent token | `/api/agent-server/v1/users` `/api/agent-server/v1/status` | `Authorization: Bearer ` | Agent 身份、client 列表拉取、状态上报。 | -当 SMTP 未配置或使用示例域名时,邮箱验证会自动关闭。 +### Session issuance paths -## MFA(TOTP) +以下路径会签发新的 session token: -- 申请 secret:`POST /api/auth/mfa/totp/provision` -- 验证并启用:`POST /api/auth/mfa/totp/verify` -- 关闭 MFA:`POST /api/auth/mfa/disable` +| 入口 | 请求字段 | 成功返回 | 备注 | +| --- | --- | --- | --- | +| `POST /api/auth/login` | `identifier/account/username/email` + `password`,或邮箱 + `totpCode` | `message`、`token`、`access_token`、`expiresAt`、`expires_in`、`user` | 如果用户已启用 MFA 且未提交验证码,则不会发 session,而是先返回 MFA challenge。 | +| `POST /api/auth/register/verify` | `email`、`code` | `message`、`token`、`expiresAt`、`user` 或 `verified=true` | 已创建用户走“验证邮箱并自动登录”;预注册验证码走“仅标记 verified”。 | +| `POST /api/auth/password/reset/confirm` | `token`、`password` | `message`、`token`、`expiresAt`、`user` | 重置密码成功后直接进入新会话。 | +| `POST /api/auth/mfa/verify` | `mfa_ticket/mfaToken`、`code/totpCode` | `message`、`token`、`access_token`、`expiresAt`、`expires_in`、`user` | 登录后补做 MFA 的完成步骤。 | +| `POST /api/auth/mfa/totp/verify` | `token`、`code` | `message`、`token`、`expiresAt`、`user` | 首次启用 TOTP 成功后直接签发 session。 | +| `GET /api/auth/oauth/callback/:provider` | query `code` | `307` redirect 到前端 `/login?exchange_code=...` | callback 自身不直接输出 JSON,而是先创建 session,再下发一次性 exchange code。 | -登录接口在部分场景会返回 `mfaToken`,用于后续验证。 +### 注册与邮箱验证 -## JWT 令牌服务(可选) +#### `POST /api/auth/register/send` -启用 `auth.enable: true` 后提供: -- `POST /api/auth/token/exchange`:使用 OAuth 回调签发的一次性 `exchange_code` 换取真实会话 token -- `POST /api/auth/token/refresh`:刷新 access token +| 项 | 内容 | +| --- | --- | +| 请求字段 | `email` | +| 成功返回 | `200 {"message":"verification email sent"}` | +| 前置条件 | 邮箱格式合法;不在 blacklist;若邮箱已存在且未验证,则继续发送验证邮件。 | +| 失败返回 | `invalid_request`、`invalid_email`、`email_blacklisted`、`smtp_timeout`、`verification_failed`、`email_already_exists`。 | -注意事项: -- `token/exchange` 只接受后端签发的一次性 `exchange_code`,不再接受调用方自报 `user_id/email/roles` -- `token/exchange` 返回的 `token`/`access_token` 是同一个真实会话 token,供前端 BFF 写入 `xc_session` -- 当前版本多数保护路由仍使用会话 token,JWT refresh 仅保留给 `token/refresh` -- 若开启 JWT 中间件,业务逻辑仍可能需要会话 token;因此控制面应优先走会话模型 +#### `POST /api/auth/register` -建议:若主要使用会话认证,请将 `auth.enable` 设为 `false`。 +| 项 | 内容 | +| --- | --- | +| 请求字段 | `name`、`email`、`password`、`code` | +| 成功返回 | `201 {"message":"registration successful","user":...}` | +| 前置条件 | `name` 非空;邮箱合法;密码长度至少 8;若启用邮箱验证则 `code` 必须存在且匹配。 | +| 关键副作用 | 创建 `store.User`;自动 upsert 7 天 trial `store.Subscription`。 | +| 失败返回 | `name_required`、`missing_credentials`、`invalid_email`、`password_too_short`、`verification_required`、`invalid_code`、`email_already_exists`、`name_already_exists`。 | + +#### `POST /api/auth/register/verify` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | `email`、`code` | +| 成功返回 A | `200 {"message":"email verified","token":"...","expiresAt":"...","user":...}` | +| 成功返回 B | `200 {"message":"verification successful","verified":true}` | +| 分支解释 | 若命中已创建用户的 email verification,接口会把用户标记为已验证并直接签发 session;若命中注册前验证码,只做“验证码通过”标记。 | +| 失败返回 | `token_in_query`、`invalid_request`、`invalid_code`、`verification_failed`、`session_creation_failed`。 | + +### 登录、MFA challenge 与会话读取 + +#### `POST /api/auth/login` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | `identifier`、`account`、`username`、`email`、`password`、`totpCode` | +| 标准成功返回 | `message`、`token`、`access_token`、`expiresAt`、`expires_in`、`mfaRequired=false`、`user` | +| MFA challenge 返回 | `message="mfa required"`、`mfaRequired=true`、`mfaMethod="totp"`、`mfaTicket`、`mfa_ticket`、兼容字段 `mfaToken` | +| 真实规则 | 先按 `identifier -> account -> username -> email` 解析登录标识。若用户已启用 MFA 但未提交 `totpCode`,登录不发 session,只发 challenge。 | +| 失败返回 | `credentials_in_query`、`invalid_request`、`missing_credentials`、`user_not_found`、`invalid_credentials`、`password_required`、`email_not_verified`、`sandbox_no_login`、`mfa_challenge_creation_failed`。 | + +#### `POST /api/auth/mfa/verify` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | `mfa_ticket`、`mfaToken`、`code`、`totpCode`、`method` | +| 成功返回 | `message`、`token`、`access_token`、`expiresAt`、`expires_in`、`user` | +| 前置条件 | `method` 仅支持 `totp`;ticket 必须存在且未过期;用户已启用 MFA。 | +| 失败返回 | `mfa_ticket_required`、`mfa_code_required`、`unsupported_mfa_method`、`invalid_mfa_ticket`、`mfa_not_enabled`、`invalid_mfa_code`。 | + +#### `GET /api/auth/session` + +| 项 | 内容 | +| --- | --- | +| 认证 | 需要有效 session;若启用 token middleware,还会先经过 JWT + active-user 检查。 | +| 成功返回 | `{"user": ...}`,其中可能附带 `tenantId`、`tenants`、XWorkmate access 视图。 | +| 失败返回 | `session_token_required`、`invalid_session`、`session_user_lookup_failed`、`account_suspended`。 | + +#### `DELETE /api/auth/session` + +| 项 | 内容 | +| --- | --- | +| 认证 | 需要有效 session。 | +| 成功返回 | `204 No Content` | +| 副作用 | 删除进程内 session,并清空 cookie。 | + +### TOTP 启用、查询与关闭 + +#### `POST /api/auth/mfa/totp/provision` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | `token`、`issuer`、`account` | +| 成功返回 | `secret`、`otpauth_url`、`issuer`、`account`、`mfaToken`、`mfa`、`user` | +| 入口模式 | 可使用已有 `mfa token`,也可使用当前 session 自动创建新的 MFA challenge。 | +| 失败返回 | `invalid_request`、`invalid_mfa_token`、`mfa_token_required`、`invalid_session`、`mfa_already_enabled`、`read_only_account`、`mfa_secret_generation_failed`、`mfa_setup_failed`。 | + +#### `POST /api/auth/mfa/totp/verify` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | `token`、`code` | +| 成功返回 | `{"message":"mfa_verified","token":"...","expiresAt":"...","user":...}` | +| 特殊失败 | 多次错误会进入 `429 {"error":"mfa_challenge_locked","retryAt":"...","mfaToken":"..."}`。 | +| 常见失败 | `mfa_token_required`、`invalid_mfa_token`、`mfa_secret_missing`、`mfa_code_required`、`invalid_mfa_code`、`mfa_update_failed`。 | + +#### `GET /api/auth/mfa/status` + +| 项 | 内容 | +| --- | --- | +| 输入来源 | query `token` / `identifier` / `email`,header `X-MFA-Token`,或 `Authorization` session token。 | +| 成功返回 | `enabled`、`mfa`、`user`。若按 identifier 查询且用户不存在,返回 `200 {"mfa_enabled":false}`。 | +| 失败返回 | `mfa_status_failed`、`mfa_token_required`。 | + +#### `POST /api/auth/mfa/disable` + +| 项 | 内容 | +| --- | --- | +| 认证 | 当前 session token,优先从 `Authorization` 读取,也接受 query `token`。 | +| 成功返回 | `{"message":"mfa_disabled","user":...}` | +| 失败返回 | `session_token_required`、`invalid_session`、`mfa_disable_failed`、`mfa_not_enabled`、`read_only_account`。 | + +### OAuth 与 exchange code + +#### `GET /api/auth/oauth/login/:provider` + +| 项 | 内容 | +| --- | --- | +| 路径参数 | `provider`,当前实现支持 `github` 与 `google`。 | +| 成功返回 | `307 Temporary Redirect` 到 provider authorization URL。 | +| 失败返回 | `provider_not_found`。 | + +#### `GET /api/auth/oauth/callback/:provider` + +| 项 | 内容 | +| --- | --- | +| 输入 | query `code`,可选由 `state` 带回前端 URL。 | +| 成功行为 | 交换 provider token,拉取 profile,创建或复用用户,确保 `store.Identity` 绑定,然后签发 session + 一次性 exchange code,最后 `307` 跳转到前端 `/login?exchange_code=...`。 | +| 失败返回 | `provider_not_found`、`code_missing`、`oauth_exchange_failed`、`fetch_profile_failed`、`email_missing`、`email_not_verified`、`store_error`、`user_creation_failed`、`session_creation_failed`、`exchange_code_creation_failed`。 | + +#### `POST /api/auth/token/exchange` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | `exchange_code` | +| 成功返回 | `token`、`access_token`、`token_type`、`expiresAt`、`expires_in`、`user` | +| 语义 | 不是按 user claims 自签 token,而是把 callback 阶段暂存的一次性 code 换回真实 session。 | +| 失败返回 | `invalid_request`、`invalid_exchange_code`、`session_user_lookup_failed`。 | + +### JWT refresh + +#### `POST /api/auth/token/refresh` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | `refresh_token` | +| 成功返回 | `access_token`、`token_type="Bearer"`、`expires_in` | +| 前置条件 | `h.tokenService != nil`;refresh token 合法且未过期。 | +| 失败返回 | `token_service_unavailable`、`invalid_request`、`invalid_refresh_token`。 | + +#### `POST /api/auth/refresh` + +别名路由,行为与 `POST /api/auth/token/refresh` 完全一致。 + +### 密码重置 + +#### `POST /api/auth/password/reset` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | `email` | +| 成功返回 | `202 {"message":"if the account exists a reset email will be sent"}` | +| 实现说明 | handler 本身按邮箱驱动;但该路由被挂在 authProtected 组下,因此启用 JWT middleware 后会多一层前置认证。 | +| 失败返回 | `email_in_query`、`invalid_request`、`email_required`、`password_reset_failed`、`read_only_account`。 | + +#### `POST /api/auth/password/reset/confirm` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | `token`、`password` | +| 成功返回 | `message`、`token`、`expiresAt`、`user` | +| 前置条件 | password 长度至少 8;reset token 有效;若用户是 demo/read-only account 则拒绝。 | +| 失败返回 | `credentials_in_query`、`invalid_request`、`password_too_short`、`invalid_token`、`password_reset_failed`、`read_only_account`、`session_creation_failed`。 | + +### XWorkmate profile 与 Vault-backed secrets + +#### `GET /api/auth/xworkmate/profile` + +| 项 | 内容 | +| --- | --- | +| 成功返回 | `edition`、`tenant`、`membershipRole`、`profileScope`、`canEditIntegrations`、`canManageTenant`、`profile`、`tokenConfigured` | +| profile 字段 | `BRIDGE_SERVER_URL`、`bridgeServerOrigin`、`vaultUrl`、`vaultNamespace`、`vaultSecretPath`、`vaultSecretKey`、`secretLocators`、`apisixUrl` | +| 失败返回 | `tenant_membership_required`、`tenant_not_found`、`xworkmate_context_failed`、`xworkmate_profile_read_failed`。 | + +#### `GET /api/auth/xworkmate/profile/sync` + +| 项 | 内容 | +| --- | --- | +| 成功返回 | `BRIDGE_SERVER_URL`、`BRIDGE_AUTH_TOKEN` | +| 语义 | 给 bridge / desktop sync 使用的精简同步视图,不返回完整 profile。 | +| 失败返回 | `tenant_membership_required`、`tenant_not_found`、`bridge_server_url_unavailable`、`bridge_auth_token_unavailable`。 | + +#### `PUT /api/auth/xworkmate/profile` + +| 项 | 内容 | +| --- | --- | +| 请求字段 | 允许直接传 profile payload,或 `{ "profile": {...} }` | +| 禁止字段 | raw token/password/api key 不允许持久化;命中时返回 `token_persistence_forbidden`。 | +| 成功返回 | 与 `GET /xworkmate/profile` 同形。 | +| 失败返回 | `xworkmate_profile_forbidden`、`read_only_account`、`invalid_request`、`token_persistence_forbidden`、`xworkmate_profile_write_failed`。 | + +#### `GET /api/auth/xworkmate/secrets` + +| 项 | 内容 | +| --- | --- | +| 成功返回 | `tenant`、`membershipRole`、`profileScope`、`canEditIntegrations`、`vaultBackendEnabled`、`tokenConfigured`、`secrets` | +| `secrets[]` 元素 | `target`、`provider`、`secretPath`、`secretKey`、`configured`、`required` 等元数据;绝不返回 raw secret。 | +| 失败返回 | `xworkmate_secret_read_failed`、`xworkmate_context_failed`、`tenant_not_found`。 | + +#### `PUT /api/auth/xworkmate/secrets/:target` + +| 项 | 内容 | +| --- | --- | +| 路径参数 | `target`,例如 bridge auth token 对应的 secret target。 | +| 请求字段 | `value` | +| 成功返回 | `secret`、`profileScope`、`tokenConfigured` | +| 失败返回 | `xworkmate_secret_forbidden`、`read_only_account`、`xworkmate_secret_unknown_target`、`xworkmate_secret_value_required`、`xworkmate_secret_write_failed`、`xworkmate_profile_write_failed`。 | + +#### `DELETE /api/auth/xworkmate/secrets/:target` + +| 项 | 内容 | +| --- | --- | +| 路径参数 | `target` | +| 成功返回 | `secret`、`profileScope`、`tokenConfigured` | +| 语义 | 删除 Vault/backend secret,但保留 locator 元数据。 | +| 失败返回 | `xworkmate_secret_forbidden`、`read_only_account`、`xworkmate_secret_unknown_target`、`xworkmate_secret_delete_failed`。 | + +## English + +### Current Auth Model + +`accounts.svc.plus` is not a pure JWT API. It is: + +- session-first for the current control plane, +- primarily called through the `xc_session` cookie or `Authorization: Bearer `, +- optionally wrapped by JWT middleware when `auth.enable` is on, +- protected by an internal service token for `/api/internal/*`, +- and protected by agent tokens for `/api/agent-server/v1/users|status`. + +So “JWT enabled” does not mean “business logic runs only on JWT”; many handlers still resolve sessions explicitly. + +### Authentication Modes + +| Mode | Example APIs | Input location | Successful outcome | +| --- | --- | --- | --- | +| Session token | `/api/auth/login`, `/api/auth/session`, `/api/auth/xworkmate/*` | `Authorization` header or `xc_session` cookie | User context, admin permissions, XWorkmate profile access. | +| JWT refresh | `POST /api/auth/token/refresh` | JSON body `refresh_token` | A new `access_token`. | +| OAuth exchange code | `POST /api/auth/token/exchange` | JSON body `exchange_code` | The real session token, returned in both `token` and `access_token`. | +| Internal service token | `/api/internal/*` | `Authorization: Bearer ` | Trusted service-to-service reads. | +| Agent token | `/api/agent-server/v1/users`, `/api/agent-server/v1/status` | `Authorization: Bearer ` | Agent identity, client-list reads, status reporting. | + +### Session Issuance Paths + +The following flows mint a new session token: + +| Entry point | Request fields | Success shape | Notes | +| --- | --- | --- | --- | +| `POST /api/auth/login` | `identifier/account/username/email` plus `password`, or email plus `totpCode` | `message`, `token`, `access_token`, `expiresAt`, `expires_in`, `user` | If MFA is enabled and no TOTP code is provided, the handler returns an MFA challenge instead of a session. | +| `POST /api/auth/register/verify` | `email`, `code` | `message`, `token`, `expiresAt`, `user` or `verified=true` | Existing-user email verification auto-logs the user in; pre-registration verification only marks the email as verified. | +| `POST /api/auth/password/reset/confirm` | `token`, `password` | `message`, `token`, `expiresAt`, `user` | A successful password reset immediately creates a session. | +| `POST /api/auth/mfa/verify` | `mfa_ticket/mfaToken`, `code/totpCode` | `message`, `token`, `access_token`, `expiresAt`, `expires_in`, `user` | Completes the MFA step after login. | +| `POST /api/auth/mfa/totp/verify` | `token`, `code` | `message`, `token`, `expiresAt`, `user` | First-time TOTP enablement also ends with a new session. | +| `GET /api/auth/oauth/callback/:provider` | query `code` | `307` redirect to frontend `/login?exchange_code=...` | The callback creates the session first, then issues a one-time exchange code instead of returning JSON directly. | + +### Registration And Email Verification + +#### `POST /api/auth/register/send` + +| Item | Details | +| --- | --- | +| Request fields | `email` | +| Success | `200 {"message":"verification email sent"}` | +| Preconditions | Valid email format; not blacklisted; existing unverified users can still receive verification mail. | +| Failures | `invalid_request`, `invalid_email`, `email_blacklisted`, `smtp_timeout`, `verification_failed`, `email_already_exists`. | + +#### `POST /api/auth/register` + +| Item | Details | +| --- | --- | +| Request fields | `name`, `email`, `password`, `code` | +| Success | `201 {"message":"registration successful","user":...}` | +| Preconditions | Non-empty `name`; valid email; password length at least 8; when email verification is enabled, `code` must exist and match. | +| Side effects | Creates `store.User` and upserts a 7-day trial `store.Subscription`. | +| Failures | `name_required`, `missing_credentials`, `invalid_email`, `password_too_short`, `verification_required`, `invalid_code`, `email_already_exists`, `name_already_exists`. | + +#### `POST /api/auth/register/verify` + +| Item | Details | +| --- | --- | +| Request fields | `email`, `code` | +| Success A | `200 {"message":"email verified","token":"...","expiresAt":"...","user":...}` | +| Success B | `200 {"message":"verification successful","verified":true}` | +| Branching | Existing-user verification marks the user as verified and issues a session; pre-registration verification only confirms the code. | +| Failures | `token_in_query`, `invalid_request`, `invalid_code`, `verification_failed`, `session_creation_failed`. | + +### Login, MFA Challenge, And Session Reads + +#### `POST /api/auth/login` + +| Item | Details | +| --- | --- | +| Request fields | `identifier`, `account`, `username`, `email`, `password`, `totpCode` | +| Standard success | `message`, `token`, `access_token`, `expiresAt`, `expires_in`, `mfaRequired=false`, `user` | +| MFA challenge | `message="mfa required"`, `mfaRequired=true`, `mfaMethod="totp"`, `mfaTicket`, `mfa_ticket`, compatibility field `mfaToken` | +| Behavior | Identifier resolution order is `identifier -> account -> username -> email`. If MFA is enabled and no TOTP code is provided, the handler returns a challenge instead of a session. | +| Failures | `credentials_in_query`, `invalid_request`, `missing_credentials`, `user_not_found`, `invalid_credentials`, `password_required`, `email_not_verified`, `sandbox_no_login`, `mfa_challenge_creation_failed`. | + +#### `POST /api/auth/mfa/verify` + +| Item | Details | +| --- | --- | +| Request fields | `mfa_ticket`, `mfaToken`, `code`, `totpCode`, `method` | +| Success | `message`, `token`, `access_token`, `expiresAt`, `expires_in`, `user` | +| Preconditions | Only `totp` is supported; the ticket must exist and not be expired; the user must have MFA enabled. | +| Failures | `mfa_ticket_required`, `mfa_code_required`, `unsupported_mfa_method`, `invalid_mfa_ticket`, `mfa_not_enabled`, `invalid_mfa_code`. | + +#### `GET /api/auth/session` + +| Item | Details | +| --- | --- | +| Auth | Requires a valid session. If token middleware is enabled, the request also passes JWT + active-user checks first. | +| Success | `{"user": ...}` and may include `tenantId`, `tenants`, and XWorkmate access metadata. | +| Failures | `session_token_required`, `invalid_session`, `session_user_lookup_failed`, `account_suspended`. | + +#### `DELETE /api/auth/session` + +| Item | Details | +| --- | --- | +| Auth | Requires a valid session. | +| Success | `204 No Content` | +| Side effect | Removes the process-local session and clears the cookie. | + +### TOTP Provisioning, Status, And Disable + +#### `POST /api/auth/mfa/totp/provision` + +| Item | Details | +| --- | --- | +| Request fields | `token`, `issuer`, `account` | +| Success | `secret`, `otpauth_url`, `issuer`, `account`, `mfaToken`, `mfa`, `user` | +| Entry modes | Uses an existing MFA token or can bootstrap a new MFA challenge from the current session. | +| Failures | `invalid_request`, `invalid_mfa_token`, `mfa_token_required`, `invalid_session`, `mfa_already_enabled`, `read_only_account`, `mfa_secret_generation_failed`, `mfa_setup_failed`. | + +#### `POST /api/auth/mfa/totp/verify` + +| Item | Details | +| --- | --- | +| Request fields | `token`, `code` | +| Success | `{"message":"mfa_verified","token":"...","expiresAt":"...","user":...}` | +| Special failure | Repeated failures can lock the challenge and return `429 {"error":"mfa_challenge_locked","retryAt":"...","mfaToken":"..."}`. | +| Common failures | `mfa_token_required`, `invalid_mfa_token`, `mfa_secret_missing`, `mfa_code_required`, `invalid_mfa_code`, `mfa_update_failed`. | + +#### `GET /api/auth/mfa/status` + +| Item | Details | +| --- | --- | +| Inputs | query `token` / `identifier` / `email`, header `X-MFA-Token`, or `Authorization` session token. | +| Success | `enabled`, `mfa`, `user`. If queried by identifier and the user does not exist, it returns `200 {"mfa_enabled":false}`. | +| Failures | `mfa_status_failed`, `mfa_token_required`. | + +#### `POST /api/auth/mfa/disable` + +| Item | Details | +| --- | --- | +| Auth | Current session token, preferably from `Authorization`; also accepts query `token`. | +| Success | `{"message":"mfa_disabled","user":...}` | +| Failures | `session_token_required`, `invalid_session`, `mfa_disable_failed`, `mfa_not_enabled`, `read_only_account`. | + +### OAuth And Exchange Code + +#### `GET /api/auth/oauth/login/:provider` + +| Item | Details | +| --- | --- | +| Path param | `provider`; the current code supports `github` and `google`. | +| Success | `307 Temporary Redirect` to the provider authorization URL. | +| Failure | `provider_not_found`. | + +#### `GET /api/auth/oauth/callback/:provider` + +| Item | Details | +| --- | --- | +| Input | query `code`; the `state` value may carry a frontend URL. | +| Success behavior | Exchanges the provider code, fetches the profile, creates or reuses the user, ensures the `store.Identity` binding exists, issues a session plus a one-time exchange code, then redirects to the frontend `/login?exchange_code=...`. | +| Failures | `provider_not_found`, `code_missing`, `oauth_exchange_failed`, `fetch_profile_failed`, `email_missing`, `email_not_verified`, `store_error`, `user_creation_failed`, `session_creation_failed`, `exchange_code_creation_failed`. | + +#### `POST /api/auth/token/exchange` + +| Item | Details | +| --- | --- | +| Request fields | `exchange_code` | +| Success | `token`, `access_token`, `token_type`, `expiresAt`, `expires_in`, `user` | +| Semantics | This does not mint a token from caller-provided claims. It converts the one-time code created during the OAuth callback back into the real session. | +| Failures | `invalid_request`, `invalid_exchange_code`, `session_user_lookup_failed`. | + +### JWT Refresh + +#### `POST /api/auth/token/refresh` + +| Item | Details | +| --- | --- | +| Request fields | `refresh_token` | +| Success | `access_token`, `token_type="Bearer"`, `expires_in` | +| Preconditions | `h.tokenService != nil`; the refresh token must be valid and not expired. | +| Failures | `token_service_unavailable`, `invalid_request`, `invalid_refresh_token`. | + +#### `POST /api/auth/refresh` + +Alias route with the exact same behavior as `POST /api/auth/token/refresh`. + +### Password Reset + +#### `POST /api/auth/password/reset` + +| Item | Details | +| --- | --- | +| Request fields | `email` | +| Success | `202 {"message":"if the account exists a reset email will be sent"}` | +| Implementation note | The handler itself is email-driven, but the route is mounted under the protected auth group, so enabling JWT middleware adds an extra precondition. | +| Failures | `email_in_query`, `invalid_request`, `email_required`, `password_reset_failed`, `read_only_account`. | + +#### `POST /api/auth/password/reset/confirm` + +| Item | Details | +| --- | --- | +| Request fields | `token`, `password` | +| Success | `message`, `token`, `expiresAt`, `user` | +| Preconditions | Password length at least 8; reset token must be valid; demo/read-only users are rejected. | +| Failures | `credentials_in_query`, `invalid_request`, `password_too_short`, `invalid_token`, `password_reset_failed`, `read_only_account`, `session_creation_failed`. | + +### XWorkmate Profile And Vault-Backed Secrets + +#### `GET /api/auth/xworkmate/profile` + +| Item | Details | +| --- | --- | +| Success | `edition`, `tenant`, `membershipRole`, `profileScope`, `canEditIntegrations`, `canManageTenant`, `profile`, `tokenConfigured` | +| `profile` fields | `BRIDGE_SERVER_URL`, `bridgeServerOrigin`, `vaultUrl`, `vaultNamespace`, `vaultSecretPath`, `vaultSecretKey`, `secretLocators`, `apisixUrl` | +| Failures | `tenant_membership_required`, `tenant_not_found`, `xworkmate_context_failed`, `xworkmate_profile_read_failed`. | + +#### `GET /api/auth/xworkmate/profile/sync` + +| Item | Details | +| --- | --- | +| Success | `BRIDGE_SERVER_URL`, `BRIDGE_AUTH_TOKEN` | +| Semantics | A reduced sync view used by bridge / desktop flows instead of the full profile. | +| Failures | `tenant_membership_required`, `tenant_not_found`, `bridge_server_url_unavailable`, `bridge_auth_token_unavailable`. | + +#### `PUT /api/auth/xworkmate/profile` + +| Item | Details | +| --- | --- | +| Request fields | Accepts either a raw profile payload or `{ "profile": {...} }` | +| Forbidden fields | Raw token/password/api-key persistence is rejected with `token_persistence_forbidden`. | +| Success | Same shape as `GET /xworkmate/profile`. | +| Failures | `xworkmate_profile_forbidden`, `read_only_account`, `invalid_request`, `token_persistence_forbidden`, `xworkmate_profile_write_failed`. | + +#### `GET /api/auth/xworkmate/secrets` + +| Item | Details | +| --- | --- | +| Success | `tenant`, `membershipRole`, `profileScope`, `canEditIntegrations`, `vaultBackendEnabled`, `tokenConfigured`, `secrets` | +| `secrets[]` | Contains `target`, `provider`, `secretPath`, `secretKey`, `configured`, `required`, and similar metadata. It never returns the raw secret value. | +| Failures | `xworkmate_secret_read_failed`, `xworkmate_context_failed`, `tenant_not_found`. | + +#### `PUT /api/auth/xworkmate/secrets/:target` + +| Item | Details | +| --- | --- | +| Path param | `target`, for example the bridge-auth-token secret target. | +| Request fields | `value` | +| Success | `secret`, `profileScope`, `tokenConfigured` | +| Failures | `xworkmate_secret_forbidden`, `read_only_account`, `xworkmate_secret_unknown_target`, `xworkmate_secret_value_required`, `xworkmate_secret_write_failed`, `xworkmate_profile_write_failed`. | + +#### `DELETE /api/auth/xworkmate/secrets/:target` + +| Item | Details | +| --- | --- | +| Path param | `target` | +| Success | `secret`, `profileScope`, `tokenConfigured` | +| Semantics | Deletes the Vault/backend secret while preserving locator metadata. | +| Failures | `xworkmate_secret_forbidden`, `read_only_account`, `xworkmate_secret_unknown_target`, `xworkmate_secret_delete_failed`. | diff --git a/docs/api/endpoints.md b/docs/api/endpoints.md index 1947285..7a5ef8b 100644 --- a/docs/api/endpoints.md +++ b/docs/api/endpoints.md @@ -1,49 +1,150 @@ -# 接口列表 +# Endpoint Matrix / 接口矩阵 -## 公共 +本页采用单份双语矩阵,避免中文表和英文表在后续维护中漂移。每一行都同时给出中文 / English 描述。 -- `GET /healthz`:健康检查 +## 1. 健康与版本 / Health And Version -## 账号认证(/api/auth) +| 方法 / Method | 路径 / Path | Owner file | 认证 / Auth | 请求参数 / Request | 成功返回 / Success | 主要依赖 / Main dependencies | +| --- | --- | --- | --- | --- | --- | --- | +| `GET` | `/healthz` | `api/api.go` | 公开 / Public | 无 / None | `200 {"status":"ok"}` | 无业务依赖 / No business dependency | +| `GET` | `/api/ping` | `api/api.go` | 公开 / Public | 无 / None | `200 {"status","image","tag","commit","version"}` | 运行时 `IMAGE` 环境变量解析 / runtime `IMAGE` parsing | -- `POST /api/auth/register`:注册 -- `POST /api/auth/register/send`:发送邮箱验证码 -- `POST /api/auth/register/verify`:验证邮箱验证码 -- `POST /api/auth/login`:登录 -- `POST /api/auth/token/exchange`:一次性 OAuth `exchange_code` 换取真实会话 token -- `POST /api/auth/token/refresh`:刷新 access token +## 2. 公共认证入口与公共读取 / Public Auth Entry And Public Reads -### 需要会话(或受保护) +| 方法 / Method | 路径 / Path | Owner file | 认证 / Auth | 请求参数 / Request | 成功返回 / Success | 主要依赖 / Main dependencies | +| --- | --- | --- | --- | --- | --- | --- | +| `POST` | `/api/auth/register` | `api/api.go` | 公开 / Public | body:`name,email,password,code` | `201 {"message","user"}` | `store.Store`, registration verification cache, subscription upsert | +| `POST` | `/api/auth/register/send` | `api/api.go` | 公开 / Public | body:`email` | `200 {"message":"verification email sent"}` | `store.Store`, `EmailSender`, registration/email verification state | +| `POST` | `/api/auth/register/verify` | `api/api.go` | 公开 / Public | body:`email,code` | `200 {"message","verified"}` or `{"message","token","expiresAt","user"}` | verification caches, `store.Store`, session store | +| `POST` | `/api/auth/login` | `api/api.go` | 公开 / Public | body:`identifier/account/username/email,password,totpCode` | `200 {"message","token","access_token","expiresAt","expires_in","user"}` or MFA challenge payload | `store.Store`, bcrypt, MFA challenge cache, session store | +| `POST` | `/api/auth/mfa/verify` | `api/api.go` | 公开 / Public | body:`mfa_ticket/mfaToken,code/totpCode,method` | `200 {"message","token","access_token","expiresAt","expires_in","user"}` | MFA challenge cache, `store.Store`, session store | +| `POST` | `/api/auth/token/exchange` | `api/api.go` | 公开 / Public | body:`exchange_code` | `200 {"token","access_token","token_type","expiresAt","expires_in","user"}` | OAuth exchange-code cache, session store, `store.Store` | +| `GET` | `/api/auth/oauth/login/:provider` | `api/api.go` | 公开 / Public | path:`provider` | `307` redirect to provider auth URL | configured `auth.OAuthProvider` | +| `GET` | `/api/auth/oauth/callback/:provider` | `api/api.go` | 公开 / Public | path:`provider`; query:`code,state?` | `307` redirect to frontend `/login?exchange_code=...` | `OAuthProvider`, `store.Store`, identity binding, session store | +| `POST` | `/api/auth/token/refresh` | `api/api.go` | 公开 / Public | body:`refresh_token` | `200 {"access_token","token_type","expires_in"}` | optional `auth.TokenService` | +| `POST` | `/api/auth/refresh` | `api/api.go` | 公开 / Public | body:`refresh_token` | same as `/token/refresh` | optional `auth.TokenService` | +| `GET` | `/api/auth/mfa/status` | `api/api.go` | 公开入口;可用 session 或 MFA token / public entry using session or MFA token | query:`token,identifier,email`; header:`X-MFA-Token`; `Authorization` optional | `200 {"enabled","mfa","user"}` or `{"mfa_enabled":false}` | MFA challenge cache, session store, `store.Store` | +| `GET` | `/api/auth/sync/config` | `api/config_sync.go` | handler 内要求 session / session enforced in handler | query:`since_version` | `200 {"schema_version","changed","version","updated_at","profiles","nodes","rendered_json","dns","meta","digest","warnings"}` | session store, `store.Store`, agent status reader, xray renderer | +| `POST` | `/api/auth/sync/ack` | `api/config_sync.go` | handler 内要求 session / session enforced in handler | body:`version,device_id,applied_at` | `200 {"acked","version","device_id","user_id","received_at"}` | session store, `store.Store` | +| `GET` | `/api/auth/homepage-video` | `api/homepage_video.go` | 公开 / Public | host/header context only | `200 {"resolved":{"videoUrl","posterUrl",...}}` | GORM-backed homepage video settings | +| `GET` | `/api/auth/sandbox/binding` | `api/sandbox_binding_public.go` | session 或 internal service token / session or internal service token | 无 / None | `200 {"address","updatedAt"}` | session store or internal token, GORM DB | -- `GET /api/auth/session`:获取当前会话用户 -- `DELETE /api/auth/session`:注销 -- `GET /api/auth/xworkmate/profile`:获取 XWorkmate 非敏感 profile / locator / tokenConfigured -- `PUT /api/auth/xworkmate/profile`:更新 XWorkmate 非敏感 profile / locator -- `GET /api/auth/xworkmate/secrets`:获取 XWorkmate Vault-backed secret 状态(不返回原文) -- `PUT /api/auth/xworkmate/secrets/:target`:写入指定 XWorkmate secret 到 Vault(不返回原文) -- `DELETE /api/auth/xworkmate/secrets/:target`:删除指定 XWorkmate secret,同时保留 locator 元数据 -- `POST /api/auth/mfa/totp/provision`:申请 MFA TOTP secret -- `POST /api/auth/mfa/totp/verify`:验证 MFA TOTP -- `POST /api/auth/mfa/disable`:关闭 MFA -- `GET /api/auth/mfa/status`:查询 MFA 状态 -- `POST /api/auth/password/reset`:发起密码重置(需要登录) -- `POST /api/auth/password/reset/confirm`:确认密码重置 -- `GET /api/auth/subscriptions`:订阅列表 -- `POST /api/auth/subscriptions`:订阅 upsert -- `POST /api/auth/subscriptions/cancel`:取消订阅 -- `POST /api/auth/config/sync`:配置同步(当前返回未实现) -- `GET /api/auth/admin/settings`:获取权限矩阵 -- `POST /api/auth/admin/settings`:更新权限矩阵 -- `GET /api/auth/admin/users/metrics`:用户指标 -- `GET /api/auth/admin/agents/status`:Agent 状态 +## 3. 会话主路径接口 / Session-Centric Protected Routes -> 说明:`/api/auth/admin/*` 需要管理员或运维角色。 +说明 / Note: -## Agent API(/api/agent-server/v1) +- 这些接口挂在 `authProtected` 组下。 +- 当 `tokenService` 启用时,会先经过 JWT middleware 与 `RequireActiveUser`。 +- 但多数 handler 仍继续读取 session,所以文档中的 auth 描述仍按“session-first”记录。 -- `GET /api/agent-server/v1/nodes`:获取已注册节点列表(用户会话鉴权) -- `GET /api/agent-server/v1/users`:获取 Xray 客户端列表 -- `POST /api/agent-server/v1/status`:上报 Agent 状态 +| 方法 / Method | 路径 / Path | Owner file | 认证 / Auth | 请求参数 / Request | 成功返回 / Success | 主要依赖 / Main dependencies | +| --- | --- | --- | --- | --- | --- | --- | +| `GET` | `/api/auth/session` | `api/api.go` | session;启用时再叠加 JWT / session plus optional JWT middleware | 无 / None | `200 {"user":...}` | session store, `store.Store`, XWorkmate access builder | +| `DELETE` | `/api/auth/session` | `api/api.go` | session / Session | 无 / None | `204 No Content` | session store | +| `GET` | `/api/auth/xworkmate/profile` | `api/xworkmate.go` | session / Session | host-derived tenant context | `200 {"edition","tenant","membershipRole","profileScope","canEditIntegrations","canManageTenant","profile","tokenConfigured"}` | `store.Store`, tenant resolution | +| `GET` | `/api/auth/xworkmate/profile/sync` | `api/xworkmate.go` | session / Session | host-derived tenant context | `200 {"BRIDGE_SERVER_URL","BRIDGE_AUTH_TOKEN"}` | `store.Store`, tenant resolution, vault/profile lookup | +| `PUT` | `/api/auth/xworkmate/profile` | `api/xworkmate.go` | session + tenant permission / session plus tenant permission | body:`profile` or raw profile payload | same shape as profile GET | `store.Store`, tenant membership checks | +| `GET` | `/api/auth/xworkmate/secrets` | `api/xworkmate.go` | session / Session | host-derived tenant context | `200 {"tenant","membershipRole","profileScope","canEditIntegrations","vaultBackendEnabled","tokenConfigured","secrets":[]}` | `store.Store`, Vault service status, locator metadata | +| `PUT` | `/api/auth/xworkmate/secrets/:target` | `api/xworkmate.go` | session + tenant permission / session plus tenant permission | path:`target`; body:`value` | `200 {"secret", "profileScope", "tokenConfigured"}` | `XWorkmateVaultService`, `store.Store` | +| `DELETE` | `/api/auth/xworkmate/secrets/:target` | `api/xworkmate.go` | session + tenant permission / session plus tenant permission | path:`target` | `200 {"secret", "profileScope", "tokenConfigured"}` | `XWorkmateVaultService`, `store.Store` | +| `POST` | `/api/auth/mfa/totp/provision` | `api/api.go` | session 或 mfa token / session or MFA token | body:`token,issuer,account` | `200 {"secret","otpauth_url","issuer","account","mfaToken","mfa","user"}` | MFA challenge cache, session store, `store.Store` | +| `POST` | `/api/auth/mfa/totp/verify` | `api/api.go` | MFA token / MFA token | body:`token,code` | `200 {"message","token","expiresAt","user"}` | MFA challenge cache, TOTP verify, session store | +| `POST` | `/api/auth/mfa/disable` | `api/api.go` | session / Session | header/query token | `200 {"message":"mfa_disabled","user"}` | session store, `store.Store` | +| `POST` | `/api/auth/password/reset` | `api/api.go` | 挂在 protected 组;handler 按邮箱运行 / mounted in protected group; handler logic is email-driven | body:`email` | `202 {"message":"if the account exists a reset email will be sent"}` | `store.Store`, password-reset cache, `EmailSender` | +| `POST` | `/api/auth/password/reset/confirm` | `api/api.go` | 挂在 protected 组;handler 按 reset token 运行 / mounted in protected group; handler logic is reset-token-driven | body:`token,password` | `200 {"message","token","expiresAt","user"}` | password-reset cache, bcrypt, session store, `store.Store` | +| `GET` | `/api/auth/subscriptions` | `api/api.go` | session / Session | 无 / None | `200 {"subscriptions":[...]}` | session store, `store.Store` | +| `POST` | `/api/auth/subscriptions` | `api/api.go` | session / Session | body:`externalId,provider,paymentMethod,paymentQr,kind,planId,status,meta` | `200 {"subscription":...}` | session store, `store.Store` | +| `POST` | `/api/auth/subscriptions/cancel` | `api/api.go` | session / Session | body:`externalId` | `200 {"subscription":...}` | session store, `store.Store`, optional Stripe cancel | +| `POST` | `/api/auth/stripe/checkout` | `api/stripe.go` | session / Session | body:`planId,stripePriceId,mode,productSlug,sourcePath` | `200 {"url","id"}` | session store, `store.Store`, Stripe client | +| `POST` | `/api/auth/stripe/portal` | `api/stripe.go` | session / Session | body:`returnPath` | `200 {"url","id"}` | session store, `store.Store`, Stripe client | +| `POST` | `/api/auth/config/sync` | `api/config_sync.go` | session / Session | body ignored; optional query:`since_version` | same as `GET /api/auth/sync/config` | session store, `store.Store`, agent status reader, xray renderer | +| `GET` | `/api/auth/admin/settings` | `api/api.go` | admin / operator permission on session | 无 / None | `200 {"version","matrix"}` | session store, `service.GetAdminSettings`, GORM DB | +| `POST` | `/api/auth/admin/settings` | `api/api.go` | admin permission on session | body:`version,matrix` | `200 {"version","matrix"}`; optimistic conflict returns `409 {"error","version","matrix"}` | session store, `service.SaveAdminSettings`, GORM DB | +| `GET` | `/api/auth/admin/homepage-video` | `api/homepage_video.go` | admin permission on session | 无 / None | `200 {"defaultEntry","overrides"}` | session store, homepage video GORM service | +| `PUT` | `/api/auth/admin/homepage-video` | `api/homepage_video.go` | admin permission on session | body:`defaultEntry,overrides` | `200 {"defaultEntry","overrides"}` | session store, homepage video GORM service | +| `GET` | `/api/auth/users` | `api/api.go` | admin read permission on session / admin read permission on session | 无 / None | `200 [sanitizeUser,...]` | session store, `store.Store`, admin permission guard | -- `GET /nodes` 使用用户会话(`xc_session` / Bearer session token) -- `GET /users` 与 `POST /status` 使用 Agent Token:`Authorization: Bearer ` +## 4. Auth 作用域管理员接口 / Auth-Scoped Admin Operations + +| 方法 / Method | 路径 / Path | Owner file | 认证 / Auth | 请求参数 / Request | 成功返回 / Success | 主要依赖 / Main dependencies | +| --- | --- | --- | --- | --- | --- | --- | +| `GET` | `/api/auth/admin/users/metrics` | `api/admin_users_metrics.go` | admin / operator session | query-driven filters handled by provider | `200` metrics overview payload | session store, `service.UserMetricsProvider`, admin permission guard | +| `POST` | `/api/auth/admin/users` | `api/admin_users.go` | admin session | body:`email,uuid,groups` | `201 {"message":"user_created","user":...}` | session store, `store.Store` | +| `POST` | `/api/auth/admin/users/:userId/role` | `api/api.go` | admin session | path:`userId`; body role payload | `200 {"message","user"}` | session store, `store.Store`, role validation | +| `DELETE` | `/api/auth/admin/users/:userId/role` | `api/api.go` | admin session | path:`userId` | `200 {"message","user"}` | session store, `store.Store` | +| `POST` | `/api/auth/admin/users/:userId/pause` | `api/admin_users.go` | admin session | path:`userId` | `200 {"message":"user_paused"}` | session store, `store.Store` | +| `POST` | `/api/auth/admin/users/:userId/resume` | `api/admin_users.go` | admin session | path:`userId` | `200 {"message":"user_resumed"}` | session store, `store.Store` | +| `DELETE` | `/api/auth/admin/users/:userId` | `api/admin_users.go` | admin session | path:`userId` | `200 {"message":"user_deleted"}` | session store, `store.Store` | +| `POST` | `/api/auth/admin/users/:userId/renew-uuid` | `api/admin_users.go` | admin session | path:`userId`; body:`expires_in_days,expires_at` | `200 {"message","proxy_uuid","expires_at"}` | session store, `store.Store` | +| `POST` | `/api/auth/admin/tenants/bootstrap` | `api/xworkmate.go` | root session / Root session | body:`name,adminUserId,adminEmail` | `201 {"tenant":{"id","name","edition","domain"},"member":{"id","email","role"}}` | session store, tenant/store models | +| `GET` | `/api/auth/admin/blacklist` | `api/admin_users.go` | admin session | 无 / None | `200 {"blacklist":[...]}` | session store, `store.Store` | +| `POST` | `/api/auth/admin/blacklist` | `api/admin_users.go` | admin session | body blacklist entry | `200 {"message":...}` | session store, `store.Store` | +| `DELETE` | `/api/auth/admin/blacklist/:email` | `api/admin_users.go` | admin session | path:`email` | `200 {"message":...}` | session store, `store.Store` | +| `GET` | `/api/auth/admin/sandbox/binding` | `api/admin_sandbox.go` | root/admin session depending on guard | 无 / None | `200 {"address","updatedAt"}` | session store, GORM DB | +| `POST` | `/api/auth/admin/sandbox/bind` | `api/admin_sandbox.go` | root/admin session depending on guard | body:`address` | `200 {"message","address"}` | session store, GORM DB, `agentserver.Registry` sandbox set | +| `POST` | `/api/auth/admin/assume` | `api/admin_assume.go` | root session / Root session | body:`email` | `200 {"ok":true,"assumed","token","expiresAt"}` | session store, `store.Store`, sandbox UUID rotation | +| `POST` | `/api/auth/admin/assume/revert` | `api/admin_assume.go` | root session / Root session | 无 / None | `200 {"ok":true}` | session store | +| `GET` | `/api/auth/admin/assume/status` | `api/admin_assume.go` | root session / Root session | 无 / None | `200 {"isAssuming","target"}` | session store | + +## 5. 公共 `/api/admin/*` 管理面 / Public `/api/admin/*` Admin Root + +说明 / Note: + +- 这组路由由 `registerAdminRoutes` 注册。 +- 语义与部分 `/api/auth/admin/*` 路由共享同一 handler。 +- 若启用了 token service,会先经过 JWT middleware 与 `RequireActiveUser`。 + +| 方法 / Method | 路径 / Path | Owner file | 认证 / Auth | 请求参数 / Request | 成功返回 / Success | 主要依赖 / Main dependencies | +| --- | --- | --- | --- | --- | --- | --- | +| `GET` | `/api/admin/users/metrics` | `api/admin_users_metrics.go` | admin / operator session | query filters | `200` metrics overview payload | session store, `service.UserMetricsProvider` | +| `GET` | `/api/admin/agents/status` | `api/admin_agents.go` | admin session | 无 / None | `200 {"agents":[{id,name,groups,healthy,message,users,syncRevision,updatedAt,xray{...}}]}` | `agentserver.Registry`, `store.Store` | +| `GET` | `/api/admin/traffic/nodes` | `api/accounting.go` | admin session | 无 / None | `200 {"nodes":[NodeHealthSnapshot...]}` | `store.Store` | +| `GET` | `/api/admin/traffic/accounts/:uuid` | `api/accounting.go` | admin session | path:`uuid` | `200 {"accountUuid","buckets","ledger","policy","quotaState","billingProfile"}` | `store.Store` | +| `GET` | `/api/admin/collector/status` | `api/accounting.go` | admin session | 无 / None | `200 {"checkpoints","recentBuckets"}` | `store.Store` | +| `GET` | `/api/admin/scheduler/status` | `api/accounting.go` | admin session | 无 / None | `200 {"decisions":[...]}` | `store.Store` | +| `POST` | `/api/admin/users` | `api/admin_users.go` | admin session | body:`email,uuid,groups` | `201 {"message","user"}` | session store, `store.Store` | +| `POST` | `/api/admin/users/:userId/pause` | `api/admin_users.go` | admin session | path:`userId` | `200 {"message":"user_paused"}` | session store, `store.Store` | +| `POST` | `/api/admin/users/:userId/resume` | `api/admin_users.go` | admin session | path:`userId` | `200 {"message":"user_resumed"}` | session store, `store.Store` | +| `DELETE` | `/api/admin/users/:userId` | `api/admin_users.go` | admin session | path:`userId` | `200 {"message":"user_deleted"}` | session store, `store.Store` | +| `POST` | `/api/admin/users/:userId/renew-uuid` | `api/admin_users.go` | admin session | path:`userId`; body:`expires_in_days,expires_at` | `200 {"message","proxy_uuid","expires_at"}` | session store, `store.Store` | +| `GET` | `/api/admin/blacklist` | `api/admin_users.go` | admin session | 无 / None | `200 {"blacklist":[...]}` | session store, `store.Store` | +| `POST` | `/api/admin/blacklist` | `api/admin_users.go` | admin session | body blacklist entry | `200 {"message":...}` | session store, `store.Store` | +| `DELETE` | `/api/admin/blacklist/:email` | `api/admin_users.go` | admin session | path:`email` | `200 {"message":...}` | session store, `store.Store` | +| `GET` | `/api/admin/sandbox/binding` | `api/admin_sandbox.go` | admin/root session | 无 / None | `200 {"address","updatedAt"}` | session store, GORM DB | +| `POST` | `/api/admin/sandbox/bind` | `api/admin_sandbox.go` | admin/root session | body:`address` | `200 {"message","address"}` | session store, GORM DB, `agentserver.Registry` | + +## 6. Webhook 与内部服务接口 / Webhook And Internal Service APIs + +| 方法 / Method | 路径 / Path | Owner file | 认证 / Auth | 请求参数 / Request | 成功返回 / Success | 主要依赖 / Main dependencies | +| --- | --- | --- | --- | --- | --- | --- | +| `POST` | `/api/billing/stripe/webhook` | `api/stripe.go` | Stripe webhook signature / Stripe webhook signature | raw Stripe event body | `200 {"received":true}` | Stripe webhook verifier, `store.Store` | +| `GET` | `/api/internal/public-overview` | `api/internal_public_overview.go` | internal service token | 无 / None | `200 {"registeredUsers","updatedAt"}` | `store.Store` | +| `GET` | `/api/internal/sandbox/guest` | `api/internal_sandbox_guest.go` | internal service token | 无 / None | `200 {"email","proxyUuid","proxyUuidExpiresAt"}` | `store.Store`, sandbox UUID rotation | +| `GET` | `/api/internal/network/identities` | `api/internal_network_identities.go` | internal service token | 无 / None | `200 {"generatedAt","identities":[{uuid,email,accountUuid}]}` | `store.Store` | +| `GET` | `/api/internal/policy/:accountUUID` | `api/accounting.go` | internal service token | path:`accountUUID` | `200` account policy snapshot | `store.Store` | +| `POST` | `/api/internal/nodes/heartbeat` | `api/accounting.go` | internal service token | body:`nodeId,region,lineCode,pricingGroup,statsEnabled,xrayRevision,healthy,latencyMs,errorRate,activeConnections,healthScore,sampledAt` | `204 No Content` | `store.Store` node health persistence | + +## 7. Agent 与节点发现接口 / Agent And Node Discovery APIs + +| 方法 / Method | 路径 / Path | Owner file | 认证 / Auth | 请求参数 / Request | 成功返回 / Success | 主要依赖 / Main dependencies | +| --- | --- | --- | --- | --- | --- | --- | +| `GET` | `/api/agent-server/v1/nodes` | `api/user_agents.go` | session,或 trusted internal service for sandbox / session or trusted internal service | session token or internal token; no body | `200 []VlessNode` | session store, `store.Store`, sandbox UUID rotation, agent status reader | +| `GET` | `/api/agent-server/v1/users` | `api/agent_server.go` | agent token / Agent token | header:`Authorization`; optional `X-Agent-ID`; query:`agentId` | `200 agentproto.ClientListResponse{clients,total,generatedAt}` | `agentserver.Registry`, `store.Store`, `xrayconfig.Client` projection | +| `POST` | `/api/agent-server/v1/status` | `api/agent_server.go` | agent token / Agent token | body:`agentproto.StatusReport` | `204 No Content` | `agentserver.Registry`, `store.NodeHealthSnapshot` upsert | +| `GET` | `/api/agent/nodes` | `api/user_agents.go` | legacy alias; same auth as canonical route / legacy alias; same auth as canonical route | same as `/api/agent-server/v1/nodes` | same as canonical route | same as canonical route | + +## 8. 账户读面接口 / Account Read Models + +| 方法 / Method | 路径 / Path | Owner file | 认证 / Auth | 请求参数 / Request | 成功返回 / Success | 主要依赖 / Main dependencies | +| --- | --- | --- | --- | --- | --- | --- | +| `GET` | `/api/account/usage/summary` | `api/accounting.go` | session / Session | no body; account resolved from current session user | `200 {"accountUuid","totalBytes","uplinkBytes","downlinkBytes","sourceOfTruth","currentBalance","remainingIncludedQuota","suspendState","throttleState","arrears","lastBucketAt","syncDelaySeconds","billingProfile"}` | session store, `store.Store` usage summary readers | +| `GET` | `/api/account/usage/buckets` | `api/accounting.go` | session / Session | query:`start,end`; account resolved from current session user | `200 {"accountUuid","buckets","sourceOfTruth"}` | session store, `store.Store` traffic buckets | +| `GET` | `/api/account/billing/summary` | `api/accounting.go` | session / Session | no body; account resolved from current session user | `200 {"accountUuid","quotaState","billingProfile","ledger","sourceOfTruth"}` | session store, `store.Store` quota/billing readers | +| `GET` | `/api/account/policy` | `api/accounting.go` | session / Session | no body; account resolved from current session user | `200 {"accountUuid","policyVersion","authState","rateProfile","connProfile","eligibleNodeGroups","preferredStrategy","degradeMode","expiresAt"}` | session store, `store.Store` policy snapshot reader | + +## 9. 备注 / Notes + +1. `/api/auth/*` 与 `/api/admin/*` 中很多接口最终共享同一 handler;差别主要在挂载位置和前端调用习惯,而不是业务语义。 +2. `/api/auth/password/reset*` 当前真实挂载位置会在启用 JWT middleware 时引入额外前置条件;这是“挂载策略”和“handler 语义”之间需要特别注意的实现细节。 +3. `account` 读接口与 `admin/traffic` 读接口都直接来自 `store.Store` 的事实表读取,不经过单独的 service 层聚合。 +4. 如需错误码、失败 envelope 和来源层说明,请继续查看 [errors.md](errors.md)。 diff --git a/docs/api/errors.md b/docs/api/errors.md index f257cea..8ff67ed 100644 --- a/docs/api/errors.md +++ b/docs/api/errors.md @@ -1,23 +1,353 @@ -# 错误码约定 +# Error Conventions / 错误约定 -常见格式(多数接口): +## 中文 + +### 通用原则 + +当前 API 没有单独的全局 error schema package,而是由几类 handler 约定共同形成: + +1. 大多数业务 handler 使用 `respondError`,返回统一 envelope。 +2. 一部分 internal / agent / GORM 简单 handler 直接 `c.JSON`,只返回 `error` 或 `error + message`。 +3. 因此文档层面必须区分“稳定错误结构”和“简化错误结构”,不能假设所有接口完全一致。 + +### 标准错误 envelope + +大多数认证、账户、管理员、XWorkmate、计费接口的失败响应是: ```json {"error":"code","message":"human readable message"} ``` -## 常见错误码(节选) +字段含义: -- `invalid_request`:请求体错误 -- `missing_credentials` / `credentials_in_query` -- `invalid_email` -- `password_too_short` -- `email_already_exists` / `name_already_exists` -- `invalid_session` / `session_token_required` -- `mfa_code_required` / `invalid_mfa_code` -- `token_service_unavailable` -- `invalid_exchange_code` / `invalid_refresh_token` -- `subscription_not_found` -- `agent_status_unavailable` +| 字段 | 含义 | +| --- | --- | +| `error` | 机器可读错误码,通常稳定,适合前端做分支。 | +| `message` | 面向人类的错误描述。 | -少数接口仅返回 `{"error":"..."}`。具体返回以接口实现为准,可在 `api/` 中查阅。 +### 扩展错误 envelope + +少数接口会在标准形状上附加字段,例如: + +| 场景 | 额外字段 | +| --- | --- | +| MFA 锁定 | `retryAt`、`mfaToken` | +| Admin settings 乐观并发冲突 | `version`、`matrix` | +| Sandbox binding / GORM 错误 | 有时直接返回底层 `err.Error()` 作为 `message` | +| `/api/ping` | 不走错误 envelope;是纯成功读取接口 | + +### 简化错误 envelope + +以下 handler 家族经常直接返回简化结构: + +```json +{"error":"some_code"} +``` + +或 + +```json +{"error":"some_code","message":"..."} +``` + +典型来源: + +| 文件 | 典型接口 | +| --- | --- | +| `api/agent_server.go` | `/api/agent-server/v1/users`、`/status` | +| `api/user_agents.go` | `/api/agent-server/v1/nodes`、legacy `/api/agent/nodes` | +| `api/internal_sandbox_guest.go` | `/api/internal/sandbox/guest` | +| `api/admin_sandbox.go` | `/api/auth/admin/sandbox/*`、`/api/admin/sandbox/*` | +| `internal/auth/middleware.go` | JWT / internal-service middleware 直接中断请求时 | + +### 错误来源分层 + +| 来源层 | 典型错误码 / 文本 | 说明 | +| --- | --- | --- | +| 请求绑定与参数校验 | `invalid_request`、`missing_credentials`、`invalid_email`、`password_too_short` | JSON body 缺字段、query 中带敏感参数、格式不对。 | +| Session / JWT / internal token | `session_token_required`、`invalid_session`、`missing authorization header`、`invalid or expired token`、`missing service token` | 认证前置失败。 | +| 角色 / 权限 / 账户状态 | `forbidden`、`root_only`、`account_suspended`、`read_only_account`、`root_email_enforced` | 用户存在,但当前身份不允许执行操作。 | +| 业务状态 | `email_already_exists`、`subscription_not_found`、`mfa_not_enabled`、`policy_not_found` | 领域对象状态不满足当前请求。 | +| 外部系统或后台依赖 | `verification_failed`、`xworkmate_secret_write_failed`、`stripe_cancel_failed`、`collector_status_unavailable` | SMTP、Vault、Stripe、DB、Xray render 等依赖失败。 | + +### 高频错误码索引 + +#### 认证与会话 + +| 错误码 | 常见状态码 | 来源 | 含义 | +| --- | --- | --- | --- | +| `credentials_in_query` | `400` | `api/api.go` | 登录或敏感接口不允许把凭据放在 query 中。 | +| `missing_credentials` | `400` | `api/api.go` | 登录、注册缺少关键字段。 | +| `user_not_found` | `404` | `api/api.go`、`api/user_agents.go` | 通过 identifier 或 session userID 找不到用户。 | +| `invalid_credentials` | `401` | `api/api.go` | 密码校验失败。 | +| `email_not_verified` | `401` | `api/api.go` | 邮箱尚未验证,禁止登录或 OAuth 登录。 | +| `session_token_required` | `401` | `api/api.go`、`api/xworkmate.go`、`api/admin_users_metrics.go` | 需要 session token。 | +| `invalid_session` | `401` | 多个 session-based handler | session 不存在、过期或无法匹配。 | +| `session_user_lookup_failed` | `500` | 多个 handler | session 存在,但无法回查用户。 | + +#### MFA + +| 错误码 | 常见状态码 | 来源 | 含义 | +| --- | --- | --- | --- | +| `mfa_ticket_required` | `400` | `verifyMFALogin` | 登录挑战完成接口缺少 ticket。 | +| `mfa_code_required` | `400` | `verifyMFALogin`、`verifyTOTP` | 缺少 TOTP 验证码。 | +| `invalid_mfa_ticket` | `401` | `verifyMFALogin` | 登录用 MFA challenge 已失效。 | +| `invalid_mfa_token` | `401` | `provisionTOTP`、`verifyTOTP` | TOTP provisioning / verify 的 token 不存在或过期。 | +| `invalid_mfa_code` | `401` / `500` | MFA handlers | 验证码错误,或库校验过程失败。 | +| `mfa_challenge_locked` | `429` | `verifyTOTP` | 连续错误次数过多,被暂时锁定。 | +| `mfa_not_enabled` | `400` | `verifyMFALogin`、`disableMFA` | 用户当前没有启用 MFA。 | + +#### 注册、邮箱验证、密码重置 + +| 错误码 | 常见状态码 | 来源 | 含义 | +| --- | --- | --- | --- | +| `verification_required` | `400` | `register` | 已启用邮箱验证,但未提供验证码。 | +| `invalid_code` | `400` | `register`、`verifyEmail` | 验证码错误或过期。 | +| `verification_failed` | `500` | 邮件验证流程 | SMTP 发送或用户更新失败。 | +| `email_already_exists` | `409` | `register`、`sendEmailVerification` | 邮箱已存在或已验证。 | +| `name_already_exists` | `409` | `register` | 用户名冲突。 | +| `password_reset_failed` | `500` | password reset flows | 密码重置邮件发送、用户更新或其他内部步骤失败。 | +| `invalid_token` | `400` | `confirmPasswordReset` | reset token 无效或过期。 | + +#### OAuth / token exchange / refresh + +| 错误码 | 常见状态码 | 来源 | 含义 | +| --- | --- | --- | --- | +| `provider_not_found` | `404` | OAuth routes | provider 未注册。 | +| `code_missing` | `400` | `oauthCallback` | callback query 中缺少 `code`。 | +| `oauth_exchange_failed` | `500` | `oauthCallback` | 与 provider 交换 token 失败。 | +| `fetch_profile_failed` | `500` | `oauthCallback` | 拉取 provider profile 失败。 | +| `invalid_exchange_code` | `401` | `exchangeToken` | 一次性 exchange code 无效或已过期。 | +| `token_service_unavailable` | `503` | `refreshToken` | 当前未启用 token service。 | +| `invalid_refresh_token` | `401` | `refreshToken` | refresh token 无效或已过期。 | + +#### 管理员与权限 + +| 错误码 | 常见状态码 | 来源 | 含义 | +| --- | --- | --- | --- | +| `forbidden` | `403` | `requireAdminPermission`、`RequireRole` | 用户权限不足。 | +| `root_only` | `403` | sandbox bind / assume / tenant bootstrap | 只有 root 可执行。 | +| `root_email_enforced` | `403` | `requireAdminPermission` | root role 被限制给 `admin@svc.plus`。 | +| `metrics_unavailable` | `503` / 其他 | `adminUsersMetrics` | 指标 provider 未配置或执行失败。 | +| `read_only_account` | `403` | 多个写接口 | demo/read-only 账号禁止写操作。 | +| `account_suspended` | `403` | session user checks | 账号被暂停。 | + +#### XWorkmate / Vault + +| 错误码 | 常见状态码 | 来源 | 含义 | +| --- | --- | --- | --- | +| `xworkmate_vault_unavailable` | `503` | `ensureXWorkmateVaultService` | 当前未注入 Vault backend。 | +| `tenant_membership_required` | `403` | `xworkmate.go` | 当前用户没有 tenant membership。 | +| `tenant_not_found` | `404` | `xworkmate.go` | host 解析出的 tenant 不存在。 | +| `xworkmate_profile_forbidden` | `403` | `updateXWorkmateProfile` | 无权修改 tenant integration profile。 | +| `token_persistence_forbidden` | `400` | `updateXWorkmateProfile` | 禁止把 raw token/password/api key 直接落库。 | +| `xworkmate_secret_unknown_target` | `400` | secret PUT/DELETE | `:target` 不在允许列表内。 | +| `xworkmate_secret_write_failed` | `500` | secret PUT | 写入 Vault/backend 失败。 | +| `xworkmate_secret_delete_failed` | `500` | secret DELETE | 删除 Vault/backend secret 失败。 | + +#### 计费、订阅、流量与调度 + +| 错误码 | 常见状态码 | 来源 | 含义 | +| --- | --- | --- | --- | +| `subscription_not_found` | `404` | `cancelSubscription` | 订阅不存在。 | +| `subscription_upsert_failed` | `500` | `upsertSubscription` | 订阅落库失败。 | +| `stripe_cancel_failed` | `502` | `cancelSubscription` | 调用 Stripe 取消失败。 | +| `usage_summary_unavailable` | `500` | `accountUsageSummary` | 读取使用量汇总失败。 | +| `invalid_start` / `invalid_end` | `400` | `accountUsageBuckets` | 时间范围参数不是 RFC3339。 | +| `policy_not_found` | `404` | `accountPolicy`、`internalAccountPolicy` | 账户策略快照不存在。 | +| `collector_status_unavailable` | `500` | `adminCollectorStatus` | collector 读面不可用。 | +| `scheduler_status_unavailable` | `500` | `adminSchedulerStatus` | 调度决策读面不可用。 | + +#### Agent 与内部服务 + +| 错误码 / 文本 | 常见状态码 | 来源 | 含义 | +| --- | --- | --- | --- | +| `missing_token` | `401` | `api/agent_server.go` | agent token 缺失。 | +| `invalid_token` | `401` | `api/agent_server.go` | agent token 无效。 | +| `agent_registry_unavailable` | `503` | `api/agent_server.go` | 未注入 `agentserver.Registry`。 | +| `list_users_failed` | `500` | `internalPublicOverview`、`listAgentUsers`、`internalNetworkIdentities` | 枚举用户失败。 | +| `store_not_configured` / `store_unavailable` | `503` | internal handlers | store 未配置。 | +| `sandbox_missing` | `404` | sandbox guest handlers | sandbox 用户不存在。 | + +### 调用方建议 + +1. 对 `respondError` 路由,优先按 `error` 做程序分支,`message` 仅用于展示。 +2. 对 agent/internal 路由,不要假设一定有 `message`。 +3. 对 `429 mfa_challenge_locked`,调用方应读取 `retryAt` 决定 UI 倒计时。 +4. 对 `POST /api/auth/admin/settings` 的 `409`,应读取返回的 `version` 与 `matrix` 重新加载服务端最新值。 + +## English + +### Core Rule + +The current API does not have a single shared error-schema package. Instead, error behavior is shaped by a few implementation conventions: + +1. Most business handlers use `respondError`, which produces the standard envelope. +2. Some internal / agent / simple GORM-backed handlers call `c.JSON` directly and return only `error` or `error + message`. +3. Documentation therefore has to distinguish between the stable error envelope and simplified handler-local errors. + +### Standard Error Envelope + +Most authentication, account, admin, XWorkmate, and accounting handlers return: + +```json +{"error":"code","message":"human readable message"} +``` + +Field meaning: + +| Field | Meaning | +| --- | --- | +| `error` | Machine-readable error code that is usually stable enough for frontend branching. | +| `message` | Human-readable explanation. | + +### Extended Error Envelope + +Some handlers add extra fields on top of the standard shape: + +| Scenario | Extra fields | +| --- | --- | +| MFA lockout | `retryAt`, `mfaToken` | +| Admin settings optimistic conflict | `version`, `matrix` | +| Sandbox binding / GORM errors | Sometimes the raw `err.Error()` is surfaced in `message` | +| `/api/ping` | Not an error-envelope route; it is a pure success read | + +### Simplified Error Envelope + +The following handler families commonly return: + +```json +{"error":"some_code"} +``` + +or: + +```json +{"error":"some_code","message":"..."} +``` + +Typical sources: + +| File | Example APIs | +| --- | --- | +| `api/agent_server.go` | `/api/agent-server/v1/users`, `/status` | +| `api/user_agents.go` | `/api/agent-server/v1/nodes`, legacy `/api/agent/nodes` | +| `api/internal_sandbox_guest.go` | `/api/internal/sandbox/guest` | +| `api/admin_sandbox.go` | `/api/auth/admin/sandbox/*`, `/api/admin/sandbox/*` | +| `internal/auth/middleware.go` | Direct JWT / internal-service middleware rejection responses | + +### Error Sources By Layer + +| Source layer | Typical codes / texts | Meaning | +| --- | --- | --- | +| Request binding and validation | `invalid_request`, `missing_credentials`, `invalid_email`, `password_too_short` | Bad JSON bodies, forbidden query-string credentials, invalid formats. | +| Session / JWT / internal token | `session_token_required`, `invalid_session`, `missing authorization header`, `invalid or expired token`, `missing service token` | Authentication precondition failed. | +| Role / permission / account-state checks | `forbidden`, `root_only`, `account_suspended`, `read_only_account`, `root_email_enforced` | The user exists but is not allowed to perform the action. | +| Business-state failures | `email_already_exists`, `subscription_not_found`, `mfa_not_enabled`, `policy_not_found` | Domain state does not satisfy the requested transition. | +| External or backend dependency failures | `verification_failed`, `xworkmate_secret_write_failed`, `stripe_cancel_failed`, `collector_status_unavailable` | SMTP, Vault, Stripe, DB, Xray rendering, and similar dependency failures. | + +### High-Frequency Error Index + +#### Authentication And Sessions + +| Code | Typical status | Source | Meaning | +| --- | --- | --- | --- | +| `credentials_in_query` | `400` | `api/api.go` | Sensitive credentials were sent through the query string. | +| `missing_credentials` | `400` | `api/api.go` | Required login or registration fields are missing. | +| `user_not_found` | `404` | `api/api.go`, `api/user_agents.go` | No user matches the identifier or session-derived user ID. | +| `invalid_credentials` | `401` | `api/api.go` | Password verification failed. | +| `email_not_verified` | `401` | `api/api.go` | The email is not verified, so login is blocked. | +| `session_token_required` | `401` | `api/api.go`, `api/xworkmate.go`, `api/admin_users_metrics.go` | A session token is required. | +| `invalid_session` | `401` | Multiple session-based handlers | The session does not exist, is expired, or cannot be matched. | +| `session_user_lookup_failed` | `500` | Multiple handlers | The session exists but the backing user cannot be loaded. | + +#### MFA + +| Code | Typical status | Source | Meaning | +| --- | --- | --- | --- | +| `mfa_ticket_required` | `400` | `verifyMFALogin` | The MFA completion endpoint is missing its ticket. | +| `mfa_code_required` | `400` | `verifyMFALogin`, `verifyTOTP` | No TOTP code was supplied. | +| `invalid_mfa_ticket` | `401` | `verifyMFALogin` | The login MFA challenge has expired or is invalid. | +| `invalid_mfa_token` | `401` | `provisionTOTP`, `verifyTOTP` | The provisioning / verification token does not exist or has expired. | +| `invalid_mfa_code` | `401` / `500` | MFA handlers | The code is wrong, or the verification library errored. | +| `mfa_challenge_locked` | `429` | `verifyTOTP` | Too many invalid attempts caused a temporary lockout. | +| `mfa_not_enabled` | `400` | `verifyMFALogin`, `disableMFA` | The user does not currently have MFA enabled. | + +#### Registration, Email Verification, And Password Reset + +| Code | Typical status | Source | Meaning | +| --- | --- | --- | --- | +| `verification_required` | `400` | `register` | Email verification is enabled but the code was not provided. | +| `invalid_code` | `400` | `register`, `verifyEmail` | The verification code is wrong or expired. | +| `verification_failed` | `500` | Email-verification flows | SMTP delivery or user-update work failed. | +| `email_already_exists` | `409` | `register`, `sendEmailVerification` | The email already exists or is already verified. | +| `name_already_exists` | `409` | `register` | Username conflict. | +| `password_reset_failed` | `500` | Password-reset flows | Email delivery, user update, or other internal reset steps failed. | +| `invalid_token` | `400` | `confirmPasswordReset` | The reset token is invalid or expired. | + +#### OAuth, Token Exchange, And Refresh + +| Code | Typical status | Source | Meaning | +| --- | --- | --- | --- | +| `provider_not_found` | `404` | OAuth routes | The provider is not registered. | +| `code_missing` | `400` | `oauthCallback` | The callback query does not contain `code`. | +| `oauth_exchange_failed` | `500` | `oauthCallback` | Exchanging the provider code failed. | +| `fetch_profile_failed` | `500` | `oauthCallback` | Fetching the provider profile failed. | +| `invalid_exchange_code` | `401` | `exchangeToken` | The one-time exchange code is invalid or expired. | +| `token_service_unavailable` | `503` | `refreshToken` | The token service is not enabled. | +| `invalid_refresh_token` | `401` | `refreshToken` | The refresh token is invalid or expired. | + +#### Admin And Permission Errors + +| Code | Typical status | Source | Meaning | +| --- | --- | --- | --- | +| `forbidden` | `403` | `requireAdminPermission`, `RequireRole` | The caller lacks the required permission. | +| `root_only` | `403` | Sandbox bind / assume / tenant bootstrap flows | Only the root user may perform the action. | +| `root_email_enforced` | `403` | `requireAdminPermission` | The root role is restricted to `admin@svc.plus`. | +| `metrics_unavailable` | `503` and others | `adminUsersMetrics` | The metrics provider is missing or failed. | +| `read_only_account` | `403` | Multiple write handlers | Demo/read-only accounts are blocked from writes. | +| `account_suspended` | `403` | Session user checks | The account has been suspended. | + +#### XWorkmate And Vault + +| Code | Typical status | Source | Meaning | +| --- | --- | --- | --- | +| `xworkmate_vault_unavailable` | `503` | `ensureXWorkmateVaultService` | No Vault backend has been injected. | +| `tenant_membership_required` | `403` | `xworkmate.go` | The caller has no tenant membership. | +| `tenant_not_found` | `404` | `xworkmate.go` | The host-resolved tenant does not exist. | +| `xworkmate_profile_forbidden` | `403` | `updateXWorkmateProfile` | The caller may not update the tenant integration profile. | +| `token_persistence_forbidden` | `400` | `updateXWorkmateProfile` | Raw token/password/api-key values may not be persisted directly. | +| `xworkmate_secret_unknown_target` | `400` | Secret PUT/DELETE handlers | The `:target` path value is outside the allowed set. | +| `xworkmate_secret_write_failed` | `500` | Secret PUT | Persisting the Vault/backend secret failed. | +| `xworkmate_secret_delete_failed` | `500` | Secret DELETE | Deleting the Vault/backend secret failed. | + +#### Billing, Subscription, Traffic, And Scheduler Reads + +| Code | Typical status | Source | Meaning | +| --- | --- | --- | --- | +| `subscription_not_found` | `404` | `cancelSubscription` | The subscription does not exist. | +| `subscription_upsert_failed` | `500` | `upsertSubscription` | Persisting the subscription failed. | +| `stripe_cancel_failed` | `502` | `cancelSubscription` | Stripe cancellation failed. | +| `usage_summary_unavailable` | `500` | `accountUsageSummary` | Usage summary reads failed. | +| `invalid_start` / `invalid_end` | `400` | `accountUsageBuckets` | The time-range query parameters are not valid RFC3339 timestamps. | +| `policy_not_found` | `404` | `accountPolicy`, `internalAccountPolicy` | No account policy snapshot is available. | +| `collector_status_unavailable` | `500` | `adminCollectorStatus` | Collector read models are unavailable. | +| `scheduler_status_unavailable` | `500` | `adminSchedulerStatus` | Scheduler decision reads are unavailable. | + +#### Agent And Internal-Service Errors + +| Code / text | Typical status | Source | Meaning | +| --- | --- | --- | --- | +| `missing_token` | `401` | `api/agent_server.go` | The agent token is missing. | +| `invalid_token` | `401` | `api/agent_server.go` | The agent token is invalid. | +| `agent_registry_unavailable` | `503` | `api/agent_server.go` | No `agentserver.Registry` has been injected. | +| `list_users_failed` | `500` | `internalPublicOverview`, `listAgentUsers`, `internalNetworkIdentities` | Enumerating users failed. | +| `store_not_configured` / `store_unavailable` | `503` | Internal handlers | The store has not been configured. | +| `sandbox_missing` | `404` | Sandbox guest handlers | The sandbox user does not exist. | + +### Client Guidance + +1. For `respondError`-based routes, branch on `error` first and treat `message` as display text. +2. For agent/internal routes, do not assume `message` is always present. +3. For `429 mfa_challenge_locked`, read `retryAt` and drive the UI countdown from it. +4. For `409` from `POST /api/auth/admin/settings`, reload the server-returned `version` and `matrix` before retrying. diff --git a/docs/api/overview.md b/docs/api/overview.md index e4375aa..f03dead 100644 --- a/docs/api/overview.md +++ b/docs/api/overview.md @@ -1,19 +1,213 @@ -# API 设计原则 +# API Overview / API 总览 -- REST 风格 + JSON -- 大多数接口使用错误结构:`{"error":"code","message":"..."}` -- 少数接口直接返回 `{"error":"..."}` -- 默认以会话 token 作为认证方式 +## 中文 -## 基础路径 +### 页面作用 -- 健康检查:`GET /healthz` -- 用户 API:`/api/auth/*` -- Agent API:`/api/agent-server/v1/*` +本页描述 `accounts.svc.plus` 当前 HTTP 面的总边界: -## 认证方式 +- 路由族如何划分。 +- 每组路由由哪些 `api/*.go` 文件拥有。 +- 认证链路如何叠加。 +- 成功与失败响应的公共形状是什么。 -- 会话 token:`Authorization: Bearer ` 或 `xc_session` Cookie -- JWT(可选):启用 `auth.enable` 后,对 `/api/auth/*` 保护路由增加 JWT 校验 +参数、返回体字段和逐接口依赖关系请继续阅读: -> 注意:当前实现中,大部分保护路由仍依赖会话 token。若开启 JWT 中间件,需确保请求同时满足会话逻辑(详见 `api/auth.md` 的说明)。 +- [认证与鉴权](auth.md) +- [接口矩阵](endpoints.md) +- [错误约定](errors.md) + +### 基础路由族 + +| 路由族 | 典型路径 | 主要 owner file | 说明 | +| --- | --- | --- | --- | +| 健康与版本 | `GET /healthz` `GET /api/ping` | `api/api.go` | 活性检查与运行时镜像版本信息。 | +| 公共认证入口 | `/api/auth/register` `/api/auth/login` `/api/auth/oauth/*` | `api/api.go` | 注册、登录、邮箱校验、OAuth 跳转、JWT refresh。 | +| 会话保护认证面 | `/api/auth/session` `/api/auth/xworkmate/*` `/api/auth/subscriptions` | `api/api.go` `api/xworkmate.go` `api/config_sync.go` `api/stripe.go` | 以 session token 为主路径的控制面接口。 | +| Auth 作用域下的管理员接口 | `/api/auth/admin/*` | `api/admin_users_metrics.go` `api/admin_users.go` `api/admin_sandbox.go` `api/admin_assume.go` `api/homepage_video.go` | 给 dashboard / BFF 使用的管理员接口。 | +| 公共 `/api/admin/*` 管理面 | `/api/admin/users/metrics` `/api/admin/traffic/*` | `api/admin_users_metrics.go` `api/admin_agents.go` `api/accounting.go` | 与前端约定的管理员根路径。 | +| 内部服务接口 | `/api/internal/*` | `api/internal_public_overview.go` `api/internal_sandbox_guest.go` `api/internal_network_identities.go` `api/accounting.go` | 仅供受信任服务调用,走 internal service token。 | +| Agent 控制接口 | `/api/agent-server/v1/*` | `api/agent_server.go` `api/user_agents.go` | 给 agent 或用户控制台提供节点与客户端视图。 | +| 账户读面 | `/api/account/*` | `api/accounting.go` | 使用量、账单、策略快照读取。 | +| Legacy alias | `/api/agent/nodes` | `api/user_agents.go` | 旧路径别名,规范路径是 `/api/agent-server/v1/nodes`。 | + +### 认证模型 + +#### 1. Session first + +当前控制面的主事实来源是 API 进程内 session store: + +- 登录、邮箱验证成功、密码重置确认成功、OAuth callback 都会签发 session token。 +- token 同时通过响应体字段 `token` 返回,并通过 `xc_session` cookie 下发。 +- 多数业务 handler 最终都调用 `handler.lookupSession` / `handler.requireAuthenticatedUser` 做真实鉴权。 + +#### 2. Optional JWT middleware + +`RegisterRoutes` 只在 `tokenService != nil` 时给 `/api/auth` 的保护组和 `/api/admin` 组叠加: + +- `auth.TokenService.AuthMiddleware()` +- `auth.RequireActiveUser(h.store)` + +因此真实行为是: + +- 当 `auth.enable` 关闭时,业务仍按 session 主路径工作。 +- 当 `auth.enable` 打开时,部分路由会先经过 JWT middleware,然后在 handler 内继续读取 session。 +- 这就是为什么文档统一把当前模式描述为“session-first, JWT-optional”。 + +#### 3. Internal service token + +`/api/internal/*` 统一使用 `auth.InternalAuthMiddleware()`: + +- 读取 `Authorization: Bearer ` +- 与环境变量 `INTERNAL_SERVICE_TOKEN` 比较 +- 失败时直接返回简化错误 JSON + +#### 4. Agent token + +`/api/agent-server/v1/users` 与 `/api/agent-server/v1/status` 走 `agentserver.Registry.Authenticate`: + +- 读取 `Authorization: Bearer ` +- 允许共享 token + `X-Agent-ID` / `agentId` 解析具体节点身份 +- 认证成功后进入 `Registry` 与 `store.NodeHealthSnapshot` 更新链路 + +### 响应约定 + +| 类别 | 典型形状 | 说明 | +| --- | --- | --- | +| 通用成功 | `{"message": "...", ...}` 或领域对象 JSON | 大多数 handler 直接输出业务对象,不包统一 envelope。 | +| 会话成功 | `{"token": "...", "expiresAt": "...", "user": {...}}` | 登录、邮箱验证、密码重置确认、MFA 完成等都会返回 session 信息。 | +| 通用失败 | `{"error":"code","message":"..."}` | 由 `respondError` 统一组装,是最常见错误形状。 | +| 简化失败 | `{"error":"..."}` | 多见于 agent/internal/simple GORM handlers。 | +| Agent DTO | `agentproto.ClientListResponse`、`204 No Content` | `/users` 返回结构化 DTO,`/status` 成功时无响应体。 | + +### 关键 handler owner map + +| 文件 | 负责的接口面 | +| --- | --- | +| `api/api.go` | 注册、登录、session、OAuth、MFA、订阅、权限矩阵、`/healthz`、`/api/ping`。 | +| `api/xworkmate.go` | XWorkmate profile、secret locator、Vault-backed secret 读写、tenant bootstrap。 | +| `api/config_sync.go` | `/api/auth/sync/config` 与 `/api/auth/sync/ack`。 | +| `api/stripe.go` | Stripe checkout、portal、webhook。 | +| `api/admin_users_metrics.go` | `/api/admin/*` 路由注册、用户指标权限控制。 | +| `api/admin_users.go` | 创建用户、暂停/恢复、删除、renew uuid、黑名单。 | +| `api/admin_agents.go` | 管理面 agent 状态聚合。 | +| `api/accounting.go` | `/api/account/*`、`/api/admin/traffic/*`、内部策略与心跳。 | +| `api/agent_server.go` | Agent 拉取 client 列表、上报状态。 | +| `api/user_agents.go` | 用户/控制台读取节点列表,含 sandbox 特判。 | +| `api/homepage_video.go` | 公共首页视频与管理员首页视频配置。 | +| `api/admin_sandbox.go` | Sandbox binding 读写。 | +| `api/admin_assume.go` | Root 假扮 sandbox 会话。 | +| `api/internal_*` | 内部 public overview、sandbox guest、network identities。 | + +### 当前实现注意点 + +1. `/api/auth/password/reset` 与 `/api/auth/password/reset/confirm` 被挂在 authProtected 组下,但核心业务逻辑本身仍是邮箱 / token 驱动;启用 JWT middleware 后会多一层前置校验。 +2. `/api/auth/sandbox/binding` 既接受正常会话,也接受内部服务 token,因为 Console Guest/Demo 需要在无本地浏览器状态时读取绑定信息。 +3. `/api/agent-server/v1/nodes` 没有挂在 token middleware 下,而是在 handler 内自行解析 session 或内部服务身份。 +4. `/api/ping` 的 `version`、`commit`、`tag` 来自运行时 `IMAGE` 环境变量解析结果,而不是 Git 本地状态。 + +## English + +### What This Page Covers + +This page defines the current HTTP surface of `accounts.svc.plus`: + +- how route families are split, +- which `api/*.go` files own them, +- how authentication layers stack, +- and what the shared success / error shapes look like. + +For endpoint-level parameters, response fields, and dependency wiring, continue with: + +- [Authentication](auth.md) +- [Endpoint Matrix](endpoints.md) +- [Error Conventions](errors.md) + +### Route Families + +| Route family | Example paths | Main owner files | Purpose | +| --- | --- | --- | --- | +| Health and version | `GET /healthz`, `GET /api/ping` | `api/api.go` | Liveness and runtime image-derived version metadata. | +| Public auth entrypoints | `/api/auth/register`, `/api/auth/login`, `/api/auth/oauth/*` | `api/api.go` | Registration, login, email verification, OAuth redirects, JWT refresh. | +| Session-protected auth surface | `/api/auth/session`, `/api/auth/xworkmate/*`, `/api/auth/subscriptions` | `api/api.go`, `api/xworkmate.go`, `api/config_sync.go`, `api/stripe.go` | Primary control-plane APIs built around session tokens. | +| Auth-scoped admin APIs | `/api/auth/admin/*` | `api/admin_users_metrics.go`, `api/admin_users.go`, `api/admin_sandbox.go`, `api/admin_assume.go`, `api/homepage_video.go` | Admin APIs used by the dashboard / BFF. | +| Public `/api/admin/*` admin root | `/api/admin/users/metrics`, `/api/admin/traffic/*` | `api/admin_users_metrics.go`, `api/admin_agents.go`, `api/accounting.go` | Frontend-facing admin root path. | +| Internal service APIs | `/api/internal/*` | `api/internal_public_overview.go`, `api/internal_sandbox_guest.go`, `api/internal_network_identities.go`, `api/accounting.go` | Trusted service-to-service APIs protected by the internal service token. | +| Agent control APIs | `/api/agent-server/v1/*` | `api/agent_server.go`, `api/user_agents.go` | Node and client views for agents and the control console. | +| Account read models | `/api/account/*` | `api/accounting.go` | Usage, billing, and policy snapshot reads. | +| Legacy alias | `/api/agent/nodes` | `api/user_agents.go` | Backward-compatible alias for `/api/agent-server/v1/nodes`. | + +### Authentication Model + +#### 1. Session-first + +The primary control-plane fact source is the process-local session store: + +- login, email verification success, password reset confirmation, and OAuth callback all issue a session token, +- the token is returned in the body as `token` and also set as the `xc_session` cookie, +- most business handlers ultimately call `handler.lookupSession` or `handler.requireAuthenticatedUser`. + +#### 2. Optional JWT middleware + +`RegisterRoutes` adds these middlewares to the protected `/api/auth` group and the `/api/admin` root only when `tokenService != nil`: + +- `auth.TokenService.AuthMiddleware()` +- `auth.RequireActiveUser(h.store)` + +So the actual runtime behavior is: + +- when `auth.enable` is off, business routes still work through the session-centric path, +- when `auth.enable` is on, some routes first pass JWT middleware and then still load the session in the handler, +- which is why the current system should be described as session-first and JWT-optional. + +#### 3. Internal service token + +`/api/internal/*` is guarded by `auth.InternalAuthMiddleware()`: + +- it reads `Authorization: Bearer `, +- compares it to `INTERNAL_SERVICE_TOKEN`, +- and returns simplified JSON errors on failure. + +#### 4. Agent token + +`/api/agent-server/v1/users` and `/api/agent-server/v1/status` authenticate through `agentserver.Registry.Authenticate`: + +- they read `Authorization: Bearer `, +- support shared tokens plus `X-Agent-ID` / `agentId` to resolve the concrete node, +- and then enter the registry and node-health persistence pipeline. + +### Response Conventions + +| Category | Typical shape | Notes | +| --- | --- | --- | +| Generic success | `{"message": "...", ...}` or a domain object | Most handlers return domain JSON directly instead of a global envelope. | +| Session success | `{"token": "...", "expiresAt": "...", "user": {...}}` | Returned by login, email verification, password reset confirmation, MFA completion, and similar flows. | +| Standard failure | `{"error":"code","message":"..."}` | Produced by `respondError`; this is the most common error envelope. | +| Simplified failure | `{"error":"..."}` | Common in internal / agent / simple GORM-backed handlers. | +| Agent DTOs | `agentproto.ClientListResponse`, `204 No Content` | `/users` returns a typed DTO, while `/status` returns no body on success. | + +### Handler Ownership Map + +| File | Owned API surface | +| --- | --- | +| `api/api.go` | Registration, login, session, OAuth, MFA, subscriptions, permission matrix, `/healthz`, `/api/ping`. | +| `api/xworkmate.go` | XWorkmate profile, secret locator metadata, Vault-backed secret mutation, tenant bootstrap. | +| `api/config_sync.go` | `/api/auth/sync/config` and `/api/auth/sync/ack`. | +| `api/stripe.go` | Stripe checkout, portal, and webhook handling. | +| `api/admin_users_metrics.go` | `/api/admin/*` route registration plus metrics permission guards. | +| `api/admin_users.go` | User creation, pause/resume, delete, renew UUID, blacklist operations. | +| `api/admin_agents.go` | Aggregated admin agent status view. | +| `api/accounting.go` | `/api/account/*`, `/api/admin/traffic/*`, internal policy reads, node heartbeat ingest. | +| `api/agent_server.go` | Agent client-list reads and status reports. | +| `api/user_agents.go` | User / console node list reads, including sandbox-specific behavior. | +| `api/homepage_video.go` | Public homepage video reads plus admin homepage video settings. | +| `api/admin_sandbox.go` | Sandbox binding reads and writes. | +| `api/admin_assume.go` | Root-only sandbox assume session flow. | +| `api/internal_*` | Internal public overview, sandbox guest, and network identity reads. | + +### Important Current Behaviors + +1. `/api/auth/password/reset` and `/api/auth/password/reset/confirm` are mounted under the protected auth group even though their business logic is email / reset-token driven; enabling JWT middleware adds an extra precondition. +2. `/api/auth/sandbox/binding` accepts either a normal session or the internal service token because the Console Guest/Demo flow must read binding state without relying on browser-local state. +3. `/api/agent-server/v1/nodes` is intentionally outside token middleware and resolves sessions or trusted internal-service identity inside the handler. +4. `/api/ping` derives `version`, `commit`, and `tag` from the runtime `IMAGE` environment variable, not from local Git state. diff --git a/docs/architecture/components.md b/docs/architecture/components.md index 57ab90f..14191d0 100644 --- a/docs/architecture/components.md +++ b/docs/architecture/components.md @@ -1,30 +1,99 @@ -# 组件职责边界 +# Component Responsibilities / 组件职责边界 -## 应用入口 -- `cmd/accountsvc`:主服务入口(server/agent/server-agent) -- `cmd/createadmin`:创建/更新超级管理员 -- `cmd/migratectl`:数据库迁移与导入导出 -- `cmd/syncctl`:跨环境同步工具 +## 中文 -## API 层 -- `api/`:HTTP 路由、请求校验、响应与错误格式 -- `api/admin_*`:管理员指标与 Agent 状态接口 +### 包级职责矩阵 -## 认证与安全 -- `internal/auth`:JWT 令牌服务与中间件 -- `internal/store`:密码哈希与角色等级规范化 +| 包 / 目录 | owning responsibility | 主要输入 | 主要输出 | 直接协作对象 | +| --- | --- | --- | --- | --- | +| `cmd/accountsvc` | 进程装配、模式切换、依赖注入、后台循环启动 | `config.Config`、环境变量、数据库连接 | Gin 服务、agent loop、xray sync loop | `api`、`store`、`auth`、`service`、`agentserver`、`agentmode`、`xrayconfig` | +| `api` | HTTP 路由注册、请求校验、鉴权拼装、响应 shape | Gin request、session token、service/store 依赖 | JSON 响应、cookie、错误结构 | `store`、`auth`、`service`、`agentserver`、`xrayconfig` | +| `internal/store` | 主业务持久化抽象与实现 | 领域模型、SQL / memory state | `Store` 接口、domain types、读写行为 | `api`、`service`、`cmd/accountsvc` | +| `internal/auth` | token service、OAuth provider、中间件上下文写入 | JWT secret、OAuth config、session token | `TokenService`、`OAuthProvider`、Gin middleware | `api`、`cmd/accountsvc` | +| `internal/service` | 管理面业务逻辑与聚合读模型 | GORM DB、`Store` 适配器 | admin settings、homepage video、user metrics | `api`、`cmd/accountsvc` | +| `internal/xrayconfig` | Xray 定义模板、客户端列表渲染、周期同步 | `ClientSource`、模板、命令配置 | config JSON、sync result | `cmd/accountsvc`、`agentmode`、`agentproto` | +| `internal/agentmode` | agent 运行时:拉取用户、生成配置、上报状态 | controller URL、agent token、xray sync config | HTTP client、status reporter、local sync loop | `agentproto`、`xrayconfig` | +| `internal/agentserver` | controller 侧 agent credential 与状态注册表 | credential config、status report、store | authenticated agent identity、status snapshots | `api`、`cmd/accountsvc` | +| `internal/agentproto` | controller 与 agent 之间的稳定 DTO | client list、status report field contract | JSON payload struct | `api`、`agentmode`、`agentserver` | -## 数据存储 -- `internal/store`:Store 接口与内存/PostgreSQL 实现 -- `sql/`:schema 与 pgsync/pglogical 相关脚本 +### 依赖方向 -## 邮件与通知 -- `internal/mailer`:SMTP 发送器与 TLS 模式支持 +```text +cmd/accountsvc + -> api + -> internal/store + -> internal/auth + -> internal/service + -> internal/agentserver + -> internal/xrayconfig + -> internal/agentmode -## Xray 相关 -- `internal/xrayconfig`:Xray 配置生成与同步 -- `internal/agentmode`:Agent 控制循环(拉取用户、上报状态) -- `internal/agentserver`:Controller 侧 Agent 注册与状态管理 +api + -> internal/store + -> internal/auth + -> internal/service + -> internal/agentserver + -> internal/agentproto + -> internal/xrayconfig -## 服务层 -- `internal/service`:管理员权限矩阵与用户指标聚合 +internal/agentmode + -> internal/agentproto + -> internal/xrayconfig +``` + +### 关键所有权说明 + +- `api.handler` 是 HTTP 层的状态拥有者,负责进程内 session / MFA / verification / reset / OAuth exchange 状态。 +- `store.Store` 是业务事实层的唯一抽象,API、service、startup 逻辑都经由它读取用户、订阅、agent、计费、tenant 和 XWorkmate profile。 +- `service` 不持有 HTTP 语义,只提供“管理面配置”和“聚合指标”。 +- `agentserver.Registry` 是 controller 面的 runtime read model,不是持久化 source of truth;它会从 `store.Store` 回填,但仍以 store 为最终落点。 +- `xrayconfig.PeriodicSyncer` 只做“配置收敛”,并不知道 HTTP、账户权限或 Stripe。 + +## English + +### Package Responsibility Matrix + +| Package / directory | Owning responsibility | Main inputs | Main outputs | Direct collaborators | +| --- | --- | --- | --- | --- | +| `cmd/accountsvc` | Process composition, mode switching, dependency injection, background loop startup | `config.Config`, environment, database handles | Gin server, agent loop, xray sync loop | `api`, `store`, `auth`, `service`, `agentserver`, `agentmode`, `xrayconfig` | +| `api` | Route registration, request validation, auth composition, response shaping | Gin requests, session tokens, injected services / stores | JSON responses, cookies, error payloads | `store`, `auth`, `service`, `agentserver`, `xrayconfig` | +| `internal/store` | Primary persistence abstraction and implementations | Domain models, SQL / in-memory state | `Store` interface, domain types, persistence behavior | `api`, `service`, `cmd/accountsvc` | +| `internal/auth` | Token service, OAuth providers, middleware context population | JWT secrets, OAuth config, session tokens | `TokenService`, `OAuthProvider`, Gin middleware | `api`, `cmd/accountsvc` | +| `internal/service` | Admin-side business logic and aggregated read models | GORM DB, `Store` adapters | admin settings, homepage video, user metrics | `api`, `cmd/accountsvc` | +| `internal/xrayconfig` | Xray definition templates, client rendering, periodic sync | `ClientSource`, templates, command config | config JSON, sync results | `cmd/accountsvc`, `agentmode`, `agentproto` | +| `internal/agentmode` | Agent runtime: fetch clients, generate configs, report status | controller URL, agent token, xray sync config | HTTP client, status reporter, local sync loop | `agentproto`, `xrayconfig` | +| `internal/agentserver` | Controller-side agent credential and status registry | credential config, status reports, store | authenticated agent identities, status snapshots | `api`, `cmd/accountsvc` | +| `internal/agentproto` | Stable DTO contract between controller and agents | client list fields, status report fields | JSON payload structs | `api`, `agentmode`, `agentserver` | + +### Dependency Direction + +```text +cmd/accountsvc + -> api + -> internal/store + -> internal/auth + -> internal/service + -> internal/agentserver + -> internal/xrayconfig + -> internal/agentmode + +api + -> internal/store + -> internal/auth + -> internal/service + -> internal/agentserver + -> internal/agentproto + -> internal/xrayconfig + +internal/agentmode + -> internal/agentproto + -> internal/xrayconfig +``` + +### Key Ownership Notes + +- `api.handler` is the HTTP-layer state owner for in-process session, MFA, verification, reset, and OAuth exchange state. +- `store.Store` is the single abstraction for business facts consumed by API, services, and startup code. +- `service` is HTTP-agnostic and focuses on admin configuration plus aggregated metrics. +- `agentserver.Registry` is a controller-side runtime read model, not the durable source of truth; it hydrates from and persists back to `store.Store`. +- `xrayconfig.PeriodicSyncer` owns configuration convergence only; it does not know about HTTP, account permissions, or Stripe. diff --git a/docs/architecture/design-decisions.md b/docs/architecture/design-decisions.md index 93b8251..ee6aebe 100644 --- a/docs/architecture/design-decisions.md +++ b/docs/architecture/design-decisions.md @@ -1,30 +1,37 @@ -# 关键设计取舍 +# Design Decisions / 关键设计取舍 -## 会话存储为内存 -- 优点:实现简单、无额外依赖 -- 代价:重启丢失会话,无法横向扩展 +## 中文 -## 主业务使用原生 SQL + pgx -- `internal/store/postgres.go` 使用 `database/sql` + pgx -- 优点:可控的 SQL 与更清晰的 schema 兼容逻辑 -- 代价:代码量较高 +| 决策 | 当前实现 | 为什么这样做 | 代价 / 约束 | +| --- | --- | --- | --- | +| 会话优先于 JWT | `api` 主路径仍围绕 session token、cookie 和 `store.GetSession` 设计;JWT 只在 `auth.enable` 打开时作为增强能力出现 | 当前控制面和 BFF 依赖 session 语义,切换成本低 | 进程内会话与 DB session 并存,认证路径不是纯 JWT-only | +| 主业务持久化走 `store.Store` | API、service、startup 统一依赖 `internal/store.Store` | 隔离 memory / postgres 实现差异,并把领域事实收口到一个接口 | 接口面较大,需要维护 memory 与 postgres 两套实现 | +| GORM 只用于管理面模型 | admin settings、homepage video、sandbox binding、tenant / XWorkmate 模型走 GORM;核心账号和大多数事实表仍走 store | 管理面模型变更频率低,使用 GORM 更便于 schema 演进和事务编排 | 持久化技术栈分裂,开发者需要同时理解 store 与 GORM | +| Agent 认证采用预共享 token | `agentserver.Registry` 用 credential token 的 SHA-256 digest 做认证 | 适合私网、受控部署和低复杂度 agent 接入 | 没有短期轮换协议,token 管理依赖外部配置治理 | +| Xray 配置采用“数据库事实 -> 模板渲染 -> 原子写文件” | `xrayconfig.Generator` / `PeriodicSyncer` 每次从源读取 clients 后整体替换 `clients[]` | 简化收敛模型,避免局部 patch 导致配置漂移 | 生成端必须拥有文件和命令执行权限 | +| XWorkmate secret 不落业务 profile 原文 | profile 中只保存 locator 元数据,真实 secret 通过 Vault 接口读写 | 避免 API 和数据库持久化 raw token | 运行时依赖 Vault 可用性;`/profile/sync` 在 bridge token 缺失时会返回冲突 | +| Root 账号强约束为 `admin@svc.plus` | `store.RootAdminEmail`、RBAC schema 和 `ensureRootUser` 共同约束单 root 身份 | 明确唯一 root 身份,防止 legacy admin 扩散 | 迁移旧数据时会触发自动降级 / 归一化逻辑 | -## Admin Settings 使用 GORM -- 管理权限矩阵更新频率较低 -- 使用 GORM 简化结构映射与事务处理 +### 设计结论 -## 邮件验证与 SMTP 可选 -- 未配置 SMTP 或使用示例域名时禁用验证 -- 避免测试环境误发送邮件 +- 当前系统不是“完全统一”的单一持久化技术栈,而是有意识地将“主业务事实层”和“管理面配置层”分开。 +- 认证模型也不是“完全统一”的 JWT-only 方案,而是 session-first、JWT-optional。 +- Xray 与 agent 子系统采用生成式控制模型:controller 负责事实、registry 负责 runtime read model、agent 负责落地文件与心跳。 -## Agent 认证为预共享 Token -- 通过配置中的 token 哈希验证 Agent -- 适合私有网络与受控部署场景 +## English -## Xray 配置生成方式 -- 定期从用户列表生成配置文件并原子写入 -- 支持自定义模板与验证/重启命令 +| Decision | Current implementation | Why it exists | Trade-off / constraint | +| --- | --- | --- | --- | +| Session-first over JWT-only | Core API flows still revolve around session tokens, cookies, and `store.GetSession`; JWT is optional and only active when `auth.enable` is true | The current control plane and BFF flows already depend on session semantics | Authentication is not purely JWT-based and mixes in-memory plus DB-backed session behavior | +| Primary persistence through `store.Store` | API, services, and startup code all consume `internal/store.Store` | This isolates memory vs postgres differences and centralizes domain facts | The interface surface is large and both implementations must stay aligned | +| GORM is limited to admin-side models | Admin settings, homepage video, sandbox binding, and tenant / XWorkmate models use GORM; core identity and fact tables stay in the store layer | Admin-side models change less often and are easier to evolve with GORM | Developers must understand two persistence styles | +| Agent authentication uses pre-shared tokens | `agentserver.Registry` authenticates tokens by SHA-256 digest of configured credentials | This keeps agent onboarding simple for private or controlled deployments | There is no built-in short-lived token rotation protocol | +| Xray config follows a generated convergence model | `xrayconfig.Generator` / `PeriodicSyncer` always rebuild `clients[]` from the source of truth | Full regeneration is simpler and avoids drift from partial patching | The runtime needs file write access plus validate / restart command permissions | +| XWorkmate secrets are locator-only in profiles | Profiles keep locator metadata only; raw secret values are read and written through Vault | This prevents raw tokens from being persisted in API payloads or DB rows | Runtime behavior depends on Vault availability; `/profile/sync` conflicts when bridge credentials are missing | +| Root account is strictly `admin@svc.plus` | `store.RootAdminEmail`, RBAC schema, and `ensureRootUser` enforce a single canonical root identity | This prevents root-role sprawl and keeps privilege normalization explicit | Legacy admin rows may be demoted or normalized during startup | -## JWT Token Service 作为可选能力 -- 提供 public/access/refresh 机制 -- 当前版本仍以会话 token 为主(详见 `api/auth.md`) +### Design Summary + +- The service intentionally separates the primary business fact layer from the admin-side configuration layer instead of forcing one persistence style everywhere. +- Authentication is intentionally session-first with optional JWT support rather than JWT-only. +- The Xray / agent subsystem follows a generative control model: controller owns facts, registry owns runtime read state, and agents own config-file application plus heartbeats. diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 13747ba..d872c8e 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -1,50 +1,131 @@ -# 架构总览 +# Architecture Overview / 架构总览 -Account Service 是一个单体 Go 服务,提供账号、计费与运营相关能力,同时可作为 Xray Controller 管理 Agent。 +## 中文 -## 逻辑架构(文字版) +### 系统边界 -``` -Client - └─ HTTP API (Gin) - ├─ Session / MFA / Email verification - ├─ Subscription & Admin Settings - ├─ Usage / Billing aggregation - ├─ Agent Controller (/api/agent-server/v1) - └─ Token Service (optional) - │ - ├─ Store (memory / postgres) - ├─ Admin Settings DB (GORM, same DSN) - ├─ SMTP Sender - └─ Xray Config Sync -``` +`accounts.svc.plus` 是一个以 Gin 为 HTTP 入口的 Go 单体服务。它同时承担四类职责: -## 核心数据流 +1. 账号与认证:注册、登录、会话、MFA、OAuth、密码重置。 +2. 账号控制面:管理员权限矩阵、用户管理、Sandbox 假扮、黑名单。 +3. Agent / Xray 控制:向 agent 提供客户端列表、接收 agent 心跳、按数据库状态生成 Xray 配置。 +4. 使用量与计费读面:账户流量桶、账本、配额、策略、节点健康和调度决策读取。 -1) 用户注册/登录 -- API 校验输入 → Store 持久化用户 → 生成会话 token -- 可选:发送邮件验证码/密码重置邮件 +### 主启动链路 -2) 管理权限矩阵 -- `admin_settings` 表保存模块与角色的开关 -- 通过 GORM 读写,内置缓存避免频繁查询 +`cmd/accountsvc/main.go` 的 server 路径可以概括为: -3) Xray 同步(Controller + Agent) -- Controller: 暴露用户列表与 Agent 状态接口 -- Agent: 定时拉取用户列表生成 Xray 配置,并上报状态 +1. 读取配置并选择运行模式:`server`、`agent`、`server-agent`。 +2. 初始化主 store 与管理面 GORM DB,后者负责 `admin_settings`、homepage video、sandbox binding、tenant / XWorkmate 相关模型。 +3. 应用 RBAC schema,并确保 root / review / sandbox 用户以及相关体验性账户状态满足当前契约。 +4. 根据 SMTP 配置决定是否启用真实邮件发送;未配置或是示例域名时自动退回“禁用邮件验证”。 +5. 在 `auth.enable` 为真时构造 `auth.TokenService`,否则系统仍以 session token 主路径工作。 +6. 构造 `agentserver.Registry`,将静态 credential、持久化 agent 状态、sandbox binding 预加载到内存读面。 +7. 在 `xray.sync.enabled` 为真时启动 `xrayconfig.PeriodicSyncer`,以数据库为源周期性重建 Xray 配置并执行 validate / restart 命令。 +8. 构造 `api.Option` 列表,把 store、mailer、token service、Stripe、Vault、OAuth provider、metrics provider、agent registry、GORM DB 注入 `api.RegisterRoutes`。 +9. 启动 Gin HTTP 服务,对外提供 `/healthz`、`/api/auth/*`、`/api/internal/*`、`/api/admin/*`、`/api/agent-server/v1/*` 与 `/api/account/*`。 -4) 数据同步 -- `traffic_minute_buckets` 保存分钟级流量快照,是 usage / billing 的基础事实表 -- `billing_ledger` 保存计费分录,供前端和后台页面读取 -- `account_quota_states`、`account_policy_snapshots`、`node_health_snapshots` 和 `scheduler_decisions` 共同构成控制平面的状态层 -- 所有 usage / billing 响应都来自 PostgreSQL,不依赖 Prometheus +### 运行时主数据流 -5) 工具同步 -- `migratectl`:导入/导出 YAML 快照 -- `syncctl`:通过 SSH 在不同环境间同步 +#### 1. 账号登录与会话 -## 关键边界 +- `POST /api/auth/login` 读取 `store.Store` 中的用户和密码哈希。 +- 成功后由 `handler.createSession` 在 session store 中落 token,再由 `sanitizeUser` 组装用户返回体。 +- 若用户启用 MFA,则先返回 challenge,再由 `POST /api/auth/mfa/verify` 完成会话签发。 -- 会话存储为进程内存(不可横向扩展) -- SMTP 未配置时自动关闭邮件验证 -- JWT Token Service 为可选能力,需与会话机制配合使用 +#### 2. 配置化管理面 + +- `internal/service/admin_settings.go` 使用 GORM 读写 `model.AdminSetting`。 +- API 层通过 `requireAdminPermission` + `service.GetAdminSettings` / `SaveAdminSettings` 组合出“会话鉴权 + 权限矩阵”的控制面能力。 +- `internal/service/homepage_video_settings.go` 复用同一套 GORM DB,负责首页视频默认项和域名覆盖项。 + +#### 3. Xray 配置生成 + +- Controller 模式下,`internal/xrayconfig.GormClientSource` 从数据库读取用户并转换为 `xrayconfig.Client`。 +- `xrayconfig.Generator` 把 `Definition` 模板渲染成 JSON,再替换 `inbounds[0].settings.clients`。 +- `xrayconfig.PeriodicSyncer` 周期性执行 `Generate`,可选执行 validate / restart 命令。 +- Agent 模式下,`internal/agentmode.Client` 通过 `/api/agent-server/v1/users` 获取客户端列表,复用同一套 `PeriodicSyncer` 本地生成配置并上报 `/status`。 + +#### 4. 使用量与计费读取 + +- `internal/store.Store` 暴露分钟桶、账本、配额、账单 profile、策略快照、节点健康、调度决策等读取方法。 +- `api/accounting.go` 将这些事实表组合为 `/api/account/*` 与 `/api/admin/traffic/*` 读接口。 +- 这些接口的 source of truth 是 PostgreSQL 事实表,不依赖 Prometheus 聚合。 + +### 后台循环与状态持有者 + +- `agentserver.Registry` 在内存中持有 credential digest、agent 身份、最新状态快照和 sandbox agent 集。 +- `handler` 在 API 层持有 session、MFA challenge、邮箱验证码、密码重置 token、OAuth exchange code 等进程内状态。 +- `PeriodicSyncer` 和 `agentmode.runStatusReporter` 是主要后台循环;前者负责配置文件收敛,后者负责 agent 健康上报。 + +### 当前明确边界 + +- Session、MFA challenge、邮箱验证码、OAuth exchange code 都是进程内状态,不是横向可共享存储。 +- JWT token service 是可选增强,不是当前主控制面的唯一鉴权来源;大部分业务仍围绕 session token 设计。 +- GORM 只承载管理面模型,不替代主业务 store。 +- `/api/agent/nodes` 是 legacy alias,规范路径是 `/api/agent-server/v1/nodes`。 + +## English + +### System Scope + +`accounts.svc.plus` is a Gin-based Go monolith. It owns four main responsibility areas: + +1. Identity and authentication: registration, login, sessions, MFA, OAuth, password reset. +2. Account control plane: admin permission matrix, user operations, sandbox assume flows, blacklist management. +3. Agent / Xray control: serving client lists to agents, receiving agent heartbeats, generating Xray configs from database state. +4. Usage and billing read models: traffic buckets, ledger entries, quota state, policy snapshots, node health, and scheduler decisions. + +### Main Startup Chain + +The server path in `cmd/accountsvc/main.go` is: + +1. Load config and choose runtime mode: `server`, `agent`, or `server-agent`. +2. Initialize the primary store plus a GORM-backed admin DB used for admin settings, homepage video, sandbox binding, and tenant / XWorkmate models. +3. Apply RBAC schema and normalize root / review / sandbox accounts so runtime invariants are satisfied. +4. Build the mailer if SMTP is configured; otherwise email verification is disabled automatically. +5. Build `auth.TokenService` only when `auth.enable` is true; the service still keeps session-token flows as the primary path. +6. Build `agentserver.Registry`, hydrate it from configured credentials and persisted agent rows, and preload sandbox bindings. +7. Start `xrayconfig.PeriodicSyncer` when `xray.sync.enabled` is true so Xray configs are regenerated from database state and optionally validated / restarted. +8. Build the `api.Option` list and inject store, mailer, token service, Stripe, Vault, OAuth providers, metrics provider, agent registry, and GORM DB into `api.RegisterRoutes`. +9. Start Gin and expose `/healthz`, `/api/auth/*`, `/api/internal/*`, `/api/admin/*`, `/api/agent-server/v1/*`, and `/api/account/*`. + +### Primary Runtime Flows + +#### 1. Identity and Session Flow + +- `POST /api/auth/login` reads users and password hashes from `store.Store`. +- On success, `handler.createSession` writes a session token and `sanitizeUser` shapes the user payload. +- If MFA is enabled, login first returns a challenge and `POST /api/auth/mfa/verify` completes session issuance. + +#### 2. Configurable Admin Surface + +- `internal/service/admin_settings.go` reads and writes `model.AdminSetting` through GORM. +- The API layer combines `requireAdminPermission` with `service.GetAdminSettings` / `SaveAdminSettings` to enforce session-based access plus permission-matrix rules. +- `internal/service/homepage_video_settings.go` uses the same GORM DB for default and per-domain homepage video entries. + +#### 3. Xray Config Generation + +- In controller mode, `internal/xrayconfig.GormClientSource` loads users from the database and converts them into `xrayconfig.Client` records. +- `xrayconfig.Generator` renders a `Definition` template into JSON and replaces `inbounds[0].settings.clients`. +- `xrayconfig.PeriodicSyncer` repeatedly calls `Generate` and may run validate / restart commands. +- In agent mode, `internal/agentmode.Client` fetches `/api/agent-server/v1/users`, feeds the same `PeriodicSyncer`, and reports `/status`. + +#### 4. Usage and Billing Reads + +- `internal/store.Store` exposes traffic buckets, ledger, quota, billing profile, policy snapshot, node health, and scheduler decision reads. +- `api/accounting.go` composes those facts into `/api/account/*` and `/api/admin/traffic/*` responses. +- The source of truth for those reads is PostgreSQL-backed fact tables, not Prometheus. + +### Background Loops and State Owners + +- `agentserver.Registry` owns credential digests, agent identities, latest status snapshots, and sandbox-agent flags in memory. +- API `handler` owns process-local session, MFA challenge, email verification, password reset, and OAuth exchange state. +- `PeriodicSyncer` and `agentmode.runStatusReporter` are the main background loops for config convergence and agent health reporting. + +### Current Hard Boundaries + +- Sessions, MFA challenges, email verification state, and OAuth exchange codes are process-local, not horizontally shared. +- JWT is optional and not the sole authentication source for the current control plane; most business flows are still session-centric. +- GORM is used for admin-side models only and does not replace the primary business store abstraction. +- `/api/agent/nodes` is a legacy alias; `/api/agent-server/v1/nodes` is the canonical route family. diff --git a/docs/development/code-structure.md b/docs/development/code-structure.md index 17d7920..1fc28ce 100644 --- a/docs/development/code-structure.md +++ b/docs/development/code-structure.md @@ -1,13 +1,565 @@ -# 代码组织说明 +# Code Structure / 代码结构 -- `cmd/`:可执行程序入口 -- `api/`:HTTP 接口与业务控制层 -- `internal/store/`:数据存储层(内存/PG) -- `internal/auth/`:JWT 令牌与鉴权中间件 -- `internal/mailer/`:SMTP 邮件发送 -- `internal/agentmode/`:Agent 模式实现 -- `internal/agentserver/`:Controller 侧 Agent 管理 -- `internal/xrayconfig/`:Xray 配置生成与同步 -- `internal/service/`:管理员设置与指标聚合 -- `sql/`:数据库 schema 与同步脚本 -- `scripts/`:构建与运维脚本 +## 中文 + +### 阅读顺序 + +建议按下面顺序阅读当前仓库主路径: + +1. `cmd/accountsvc/main.go` +2. `api/api.go` +3. `internal/store/store.go` +4. `internal/auth/*` +5. `internal/service/*` +6. `internal/xrayconfig/*` +7. `internal/agentserver/registry.go` +8. `internal/agentmode/*` +9. `internal/agentproto/types.go` + +### `cmd/accountsvc`(运行时装配) + +**核心职责** + +- 读取配置并决定运行模式:`server`、`agent`、`server-agent`。 +- 装配主 store、GORM admin DB、mailer、token service、OAuth provider、agent registry、xray syncer。 +- 确保 root / review / sandbox 账户与 RBAC schema 满足当前契约。 + +**关键非导出 owner** + +| 符号 | 说明 | 主要输入 | 主要输出 | +| --- | --- | --- | --- | +| `rootCmd.RunE` | CLI 主入口,负责 mode dispatch | `configPath`、`logLevel` | 调用 `runServer` / `runAgent` / `runServerAndAgent` | +| `runServer(ctx, cfg, logger)` | server 模式总装配函数 | `context.Context`、`config.Config`、`*slog.Logger` | Gin server、后台循环、依赖注入 | +| `runAgent(ctx, cfg, logger)` | agent 模式装配函数 | 同上 | `agentmode.Run(...)` | +| `openAdminSettingsDB(cfg.Store)` | 打开并迁移 GORM DB | `config.Store` | `*gorm.DB`、cleanup 闭包 | +| `applyRBACSchema(ctx, db, driver)` | 写入 RBAC / users / agents / sessions 等 schema 与 seed | `context.Context`、`*gorm.DB`、driver 名 | error | + +### `api`(HTTP 控制层) + +**文件** + +- `api/api.go`:主路由注册、auth/session/MFA/subscription/OAuth。 +- `api/admin_*.go`:admin metrics、agent status、sandbox assume/bind、用户管理。 +- `api/accounting.go`:account / traffic / policy / heartbeat 读写面。 +- `api/xworkmate*.go`:tenant/profile/secret/Vault 集成。 +- `api/stripe.go`:checkout / portal / webhook。 +- `api/homepage_video.go`、`api/config_sync.go`、`api/internal_*.go`:辅助控制面接口。 + +**核心导出符号** + +| 符号 | kind | 签名 / 字段 | 参数 | 返回 | 说明 | +| --- | --- | --- | --- | --- | --- | +| `Option` | func type | `type Option func(*handler)` | `*handler` | 无 | API 层依赖注入闭包。 | +| `WithStore` | func | `WithStore(st store.Store) Option` | `st`: 主业务 store | `Option` | 覆盖默认内存 store。 | +| `WithSessionTTL` | func | `WithSessionTTL(ttl time.Duration) Option` | `ttl`: session 生命周期 | `Option` | 控制会话签发 TTL。 | +| `WithEmailSender` | func | `WithEmailSender(sender EmailSender) Option` | `sender`: 邮件发送器 | `Option` | 注入真实或 mock mailer。 | +| `WithEmailVerification` | func | `WithEmailVerification(enabled bool) Option` | `enabled`: 是否要求邮件验证 | `Option` | 控制注册前邮箱验证逻辑。 | +| `WithEmailVerificationTTL` | func | `WithEmailVerificationTTL(ttl time.Duration) Option` | `ttl`: 验证码 TTL | `Option` | 覆盖默认邮箱验证码 TTL。 | +| `WithUserMetricsProvider` | func | `WithUserMetricsProvider(provider service.UserMetricsProvider) Option` | metrics provider | `Option` | 注入用户指标聚合器。 | +| `WithAgentStatusReader` | func | `WithAgentStatusReader(reader agentStatusReader) Option` | registry / status reader | `Option` | 注入 admin agent status 读面。 | +| `WithPasswordResetTTL` | func | `WithPasswordResetTTL(ttl time.Duration) Option` | reset TTL | `Option` | 控制密码重置 token 生命周期。 | +| `WithTokenService` | func | `WithTokenService(tokenService *auth.TokenService) Option` | token service | `Option` | 启用可选 JWT + session middleware。 | +| `WithOAuthProviders` | func | `WithOAuthProviders(providers map[string]auth.OAuthProvider) Option` | provider map | `Option` | 注册 GitHub / Google OAuth provider。 | +| `WithServerPublicURL` | func | `WithServerPublicURL(url string) Option` | public URL | `Option` | 供 sync config / VLESS URL 生成使用。 | +| `WithXrayConfigRenderer` | func | `WithXrayConfigRenderer(renderer func(*store.User) (string, string, []string, error)) Option` | 自定义 renderer | `Option` | 主要用于测试或替换 sync render。 | +| `WithOAuthFrontendURL` | func | `WithOAuthFrontendURL(url string) Option` | frontend URL | `Option` | 决定 OAuth callback 的前端跳转目标。 | +| `WithXWorkmateVaultService` | func | `WithXWorkmateVaultService(vaultService xworkmateVaultService) Option` | Vault backend | `Option` | 启用 XWorkmate secret 读写。 | +| `WithAgentRegistry` | func | `WithAgentRegistry(registry agentRegistry) Option` | agent registry | `Option` | 注入 agent auth 与 sandbox agent 读面。 | +| `WithGormDB` | func | `WithGormDB(db *gorm.DB) Option` | GORM DB | `Option` | admin / tenant / homepage video 使用。 | +| `WithStripeConfig` | func | `WithStripeConfig(cfg StripeConfig) Option` | Stripe 配置 | `Option` | 启用 Stripe 集成。 | +| `RegisterRoutes` | func | `RegisterRoutes(r *gin.Engine, opts ...Option)` | Gin engine、Option 列表 | 无 | 一次性挂载全部 HTTP 路由。 | +| `EmailMessage` | struct | `To []string`, `Subject string`, `PlainBody string`, `HTMLBody string` | N/A | N/A | API 层统一邮件消息体。 | +| `EmailSender` | interface | `Send(ctx context.Context, msg EmailMessage) error` | context、message | `error` | 邮件发送接口。 | +| `EmailSenderFunc` | func type | `func(ctx context.Context, msg EmailMessage) error` | 同上 | `error` | 函数适配器。 | +| `StripeConfig` | struct | `SecretKey`, `WebhookSecret`, `AllowedPriceIDs`, `FrontendURL` | N/A | N/A | Stripe 客户端构造参数。 | +| `XWorkmateVaultConfig` | struct | `Address`, `Token`, `Namespace`, `Mount`, `Timeout`, `HTTPClient` | N/A | N/A | XWorkmate Vault HTTP 客户端配置。 | +| `NewXWorkmateVaultService` | func | `NewXWorkmateVaultService(cfg XWorkmateVaultConfig) (xworkmateVaultService, error)` | Vault config | service、`error` | 根据配置决定返回 HTTP backend 或 `nil`。 | + +**关键非导出 owner** + +| 符号 | 说明 | +| --- | --- | +| `handler` | HTTP 层真正的状态拥有者,持有 store、session TTL、MFA challenge map、verification map、password reset map、OAuth exchange code map、metrics provider、token service、Vault service、Stripe client、agent registry 等。 | +| `respondError` | 标准错误信封输出函数,形如 `{"error":"code","message":"message"}`。 | +| `sanitizeUser` / `sanitizeSubscription` | 主 API 的稳定返回 shape 组装器。 | +| `buildXWorkmateProfileResponse` | XWorkmate profile / secret API 的统一响应组装器。 | + +### `internal/store`(领域模型与持久化抽象) + +**核心导出类型** + +| 符号 | kind | 关键字段 / 方法 | 说明 | +| --- | --- | --- | --- | +| `User` | struct | `ID`, `Name`, `Email`, `Level`, `Role`, `Groups`, `Permissions`, `EmailVerified`, `PasswordHash`, `MFATOTPSecret`, `MFAEnabled`, `ProxyUUID`, `ProxyUUIDExpiresAt` | 主账号实体。 | +| `Subscription` | struct | `ID`, `UserID`, `Provider`, `PaymentMethod`, `Kind`, `PlanID`, `ExternalID`, `Status`, `Meta` | 订阅或账单关系。 | +| `Identity` | struct | `ID`, `UserID`, `Provider`, `ExternalID` | OAuth 身份映射。 | +| `Agent` | struct | `ID`, `Name`, `Groups`, `Healthy`, `LastHeartbeat`, `ClientsCount`, `SyncRevision` | controller 持久化的 agent 状态行。 | +| `TrafficStatCheckpoint` / `TrafficMinuteBucket` / `BillingLedgerEntry` / `AccountQuotaState` / `AccountBillingProfile` / `AccountPolicySnapshot` / `NodeHealthSnapshot` / `SchedulerDecision` | struct | 计费、流量、调度读模型 | 为 `/api/account/*` 与 `/api/admin/traffic/*` 提供事实层。 | +| `Tenant` / `TenantDomain` / `TenantMembership` / `XWorkmateProfile` / `XWorkmateSecretLocator` | struct | tenant / 域名 / 成员关系 / integration profile / secret locator 元数据 | 支撑 XWorkmate 多租户与 Vault locator 设计。 | +| `Store` | interface | 见下表 | 主业务持久化抽象。 | + +**`Store` 接口方法分组** + +- 用户与身份: + - `CreateUser(ctx context.Context, user *User) error` + - `GetUserByEmail(ctx context.Context, email string) (*User, error)` + - `GetUserByID(ctx context.Context, id string) (*User, error)` + - `GetUserByName(ctx context.Context, name string) (*User, error)` + - `UpdateUser(ctx context.Context, user *User) error` + - `CreateIdentity(ctx context.Context, identity *Identity) error` + - `ListUsers(ctx context.Context) ([]User, error)` + - `DeleteUser(ctx context.Context, id string) error` + +- 订阅: + - `UpsertSubscription(ctx context.Context, subscription *Subscription) error` + - `ListSubscriptionsByUser(ctx context.Context, userID string) ([]Subscription, error)` + - `CancelSubscription(ctx context.Context, userID, externalID string, cancelledAt time.Time) (*Subscription, error)` + +- 黑名单: + - `AddToBlacklist(ctx context.Context, email string) error` + - `RemoveFromBlacklist(ctx context.Context, email string) error` + - `IsBlacklisted(ctx context.Context, email string) (bool, error)` + - `ListBlacklist(ctx context.Context) ([]string, error)` + +- 会话: + - `CreateSession(ctx context.Context, token, userID string, expiresAt time.Time) error` + - `GetSession(ctx context.Context, token string) (string, time.Time, error)` + - `DeleteSession(ctx context.Context, token string) error` + +- Agent: + - `UpsertAgent(ctx context.Context, agent *Agent) error` + - `GetAgent(ctx context.Context, id string) (*Agent, error)` + - `ListAgents(ctx context.Context) ([]*Agent, error)` + - `DeleteAgent(ctx context.Context, id string) error` + - `DeleteStaleAgents(ctx context.Context, staleThreshold time.Duration) (int, error)` + +- 流量 / 计费 / 调度: + - `UpsertTrafficStatCheckpoint(ctx context.Context, checkpoint *TrafficStatCheckpoint) error` + - `GetTrafficStatCheckpoint(ctx context.Context, nodeID, accountUUID string) (*TrafficStatCheckpoint, error)` + - `ListTrafficStatCheckpoints(ctx context.Context) ([]TrafficStatCheckpoint, error)` + - `UpsertTrafficMinuteBucket(ctx context.Context, bucket *TrafficMinuteBucket) error` + - `ListTrafficMinuteBucketsByAccount(ctx context.Context, accountUUID string, start, end time.Time) ([]TrafficMinuteBucket, error)` + - `ListTrafficMinuteBuckets(ctx context.Context) ([]TrafficMinuteBucket, error)` + - `InsertBillingLedgerEntry(ctx context.Context, entry *BillingLedgerEntry) error` + - `ListBillingLedgerByAccount(ctx context.Context, accountUUID string, limit int) ([]BillingLedgerEntry, error)` + - `UpsertAccountQuotaState(ctx context.Context, state *AccountQuotaState) error` + - `GetAccountQuotaState(ctx context.Context, accountUUID string) (*AccountQuotaState, error)` + - `UpsertAccountBillingProfile(ctx context.Context, profile *AccountBillingProfile) error` + - `GetAccountBillingProfile(ctx context.Context, accountUUID string) (*AccountBillingProfile, error)` + - `UpsertAccountPolicySnapshot(ctx context.Context, snapshot *AccountPolicySnapshot) error` + - `GetLatestAccountPolicySnapshot(ctx context.Context, accountUUID string) (*AccountPolicySnapshot, error)` + - `UpsertNodeHealthSnapshot(ctx context.Context, snapshot *NodeHealthSnapshot) error` + - `ListLatestNodeHealthSnapshots(ctx context.Context) ([]NodeHealthSnapshot, error)` + - `InsertSchedulerDecision(ctx context.Context, decision *SchedulerDecision) error` + - `ListRecentSchedulerDecisions(ctx context.Context, limit int) ([]SchedulerDecision, error)` + +- Tenant / XWorkmate: + - `EnsureTenant(ctx context.Context, tenant *Tenant) error` + - `EnsureTenantDomain(ctx context.Context, domain *TenantDomain) error` + - `UpsertTenantMembership(ctx context.Context, membership *TenantMembership) error` + - `ResolveTenantByHost(ctx context.Context, host string) (*Tenant, *TenantDomain, error)` + - `ListTenantMembershipsByUser(ctx context.Context, userID string) ([]TenantMembership, error)` + - `GetTenantMembership(ctx context.Context, tenantID, userID string) (*TenantMembership, error)` + - `GetXWorkmateProfile(ctx context.Context, tenantID, userID, scope string) (*XWorkmateProfile, error)` + - `UpsertXWorkmateProfile(ctx context.Context, profile *XWorkmateProfile) error` + +**规范化与角色辅助函数** + +- `NormalizeTenantEdition(value string) string` +- `NormalizeTenantMembershipRole(value string) string` +- `NormalizeTenantDomainKind(value string) string` +- `NormalizeTenantDomainStatus(value string) string` +- `NormalizeXWorkmateProfileScope(value string) string` +- `NormalizeXWorkmateSecretLocator(locator *XWorkmateSecretLocator)` +- `NormalizeHostname(value string) string` +- `IsSharedTenantHost(host string) bool` +- `GenerateRandomTenantDomain() (string, error)` +- `IsRootRole(role string) bool` +- `IsAdminRole(role string) bool` +- `IsOperatorRole(role string) bool` +- `NewMemoryStore() Store` +- `NewMemoryStoreWithSuperAdminCounting() Store` + +### `internal/auth`(认证、OAuth、中间件) + +**核心导出符号** + +| 符号 | 签名 / 字段 | 参数 | 返回 | 说明 | +| --- | --- | --- | --- | --- | +| `TokenPair` | `PublicToken`, `AccessToken`, `RefreshToken`, `TokenType`, `ExpiresIn` | N/A | N/A | JWT 相关返回体。 | +| `Claims` | `UserID`, `Email`, `Roles`, `MFA`, `jwt.RegisteredClaims` | N/A | N/A | access token claims。 | +| `TokenConfig` | `PublicToken`, `RefreshSecret`, `AccessSecret`, `AccessExpiry`, `RefreshExpiry`, `Store` | N/A | N/A | token service 配置。 | +| `TokenService` | token / secret / expiry / store 成员 | N/A | N/A | JWT 与 session fallback 中心。 | +| `NewTokenService` | `NewTokenService(config TokenConfig) *TokenService` | config | `*TokenService` | 构造 token service。 | +| `SetStore` | `SetStore(st store.Store)` | store | 无 | 为 AuthMiddleware 注入 session store。 | +| `ValidatePublicToken` | `ValidatePublicToken(publicToken string) bool` | public token | `bool` | 校验 public token。 | +| `GeneratePublicToken` | `GeneratePublicToken(userID, email string, roles []string) string` | user identity | `string` | 返回配置中的 public token。 | +| `GenerateTokenPair` | `GenerateTokenPair(userID, email string, roles []string) (*TokenPair, error)` | user identity | token pair、error | 生成 access + refresh token。 | +| `ValidateAccessToken` | `ValidateAccessToken(accessToken string) (*Claims, error)` | JWT string | claims、error | 解析 access token。 | +| `RefreshAccessToken` | `RefreshAccessToken(refreshToken string) (string, error)` | refresh token | new access token、error | 刷新 access token。 | +| `GetAccessTokenExpiry` | `GetAccessTokenExpiry() time.Duration` | 无 | duration | 返回 access token TTL。 | +| `OAuthUserProfile` | `ID`, `Email`, `Name`, `Verified` | N/A | N/A | 统一 OAuth 用户 profile。 | +| `OAuthProvider` | `AuthCodeURL(state string) string`, `Exchange(ctx, code) (*oauth2.Token, error)`, `FetchProfile(ctx, token) (*OAuthUserProfile, error)`, `Name() string` | N/A | N/A | OAuth provider 抽象。 | +| `GitHubProvider` / `GoogleProvider` | provider struct | N/A | N/A | 具体 OAuth provider。 | +| `NewGitHubProvider` | `NewGitHubProvider(clientID, clientSecret, redirectURL string) *GitHubProvider` | OAuth config | provider | GitHub OAuth 构造。 | +| `NewGoogleProvider` | `NewGoogleProvider(clientID, clientSecret, redirectURL string) *GoogleProvider` | OAuth config | provider | Google OAuth 构造。 | +| `AuthMiddleware` | `func (s *TokenService) AuthMiddleware() gin.HandlerFunc` | token service receiver | Gin middleware | JWT 失败后 fallback 到 session store。 | +| `RequireActiveUser` | `RequireActiveUser(s store.Store) gin.HandlerFunc` | store | middleware | 拒绝 suspended user。 | +| `InternalAuthMiddleware` | `InternalAuthMiddleware() gin.HandlerFunc` | 无 | middleware | 校验 `X-Service-Token`。 | +| `RequireMFA` | `RequireMFA() gin.HandlerFunc` | 无 | middleware | 要求 MFA verified。 | +| `RequireRole` | `RequireRole(role string) gin.HandlerFunc` | role | middleware | 角色检查。 | +| `GetUserID` / `GetEmail` / `GetRoles` / `IsMFAVerified` | context getter | `*gin.Context` | 基础值 | 从 Gin context 中读取 auth state。 | +| `HTTPHandler` / `Wrap` | 轻量 handler adapter | handler | `gin.HandlerFunc` | 包内辅助适配器。 | + +### `internal/service`(管理面服务) + +**admin settings** + +- `type AdminSettings struct` + - 字段:`Version uint64`、`Matrix map[string]map[string]bool` +- `SetDB(d *gorm.DB)` + - 参数:GORM DB + - 返回:无 + - 作用:设置 service 层共享 DB 并清空缓存 +- `GetAdminSettings(ctx context.Context) (AdminSettings, error)` + - 返回:当前 permission matrix 与 version +- `SaveAdminSettings(ctx context.Context, payload AdminSettings) (AdminSettings, error)` + - 返回:新版本 settings 或版本冲突错误 + +**homepage video** + +- `type HomepageVideoEntry struct` + - 字段:`DomainKey`, `VideoURL`, `PosterURL` +- `type HomepageVideoSettings struct` + - 字段:`DefaultEntry HomepageVideoEntry`, `Overrides []HomepageVideoEntry` +- `GetHomepageVideoSettings(ctx context.Context) (HomepageVideoSettings, error)` +- `SaveHomepageVideoSettings(ctx context.Context, settings HomepageVideoSettings, updatedBy string) (HomepageVideoSettings, error)` +- `ResolveHomepageVideoEntry(ctx context.Context, host string) (HomepageVideoEntry, error)` + +**user metrics** + +- `type UserRepository interface` + - `ListUsers(ctx context.Context) ([]UserRecord, error)` +- `type SubscriptionProvider interface` + - `FetchSubscriptionStates(ctx context.Context, userIDs []string) (map[string]SubscriptionState, error)` +- `type UserRecord struct` + - `ID`, `CreatedAt`, `Active` +- `type SubscriptionState struct` + - `Active`, `ExpiresAt` +- `type UserMetricsService struct` + - 依赖:`Users UserRepository`, `Subscriptions SubscriptionProvider`, `DailyPeriods`, `WeeklyPeriods` +- `Compute(ctx context.Context) (UserMetrics, error)` + - 返回:`MetricsOverview` + `MetricsSeries` + +### `internal/xrayconfig`(配置生成与同步) + +| 符号 | 签名 / 字段 | 说明 | +| --- | --- | --- | +| `Definition` | `Base() (map[string]interface{}, error)` | Xray 基础模板抽象。 | +| `JSONDefinition` | `Raw []byte` | 以 JSON 文档实现 `Definition`。 | +| `Client` | `ID`, `Email`, `Flow` | Xray client 条目。 | +| `Generator` | `Definition`, `OutputPath`, `FileMode`, `Domain` | 根据 clients 生成配置文件。 | +| `Generate` | `Generate(clients []Client) error` | 渲染并原子写入文件。 | +| `Render` | `Render(clients []Client) ([]byte, error)` | 只返回 JSON,不写盘。 | +| `GormClientSource` | `DB *gorm.DB`, `Logger *slog.Logger` | 从 users 表读取 client。 | +| `NewGormClientSource` | `NewGormClientSource(db *gorm.DB) (*GormClientSource, error)` | 构造 GORM source。 | +| `ClientSource` | `ListClients(ctx context.Context) ([]Client, error)` | `PeriodicSyncer` 依赖的源接口。 | +| `PeriodicOptions` | `Logger`, `Interval`, `Source`, `Generators`, `ValidateCommand`, `RestartCommand`, `Runner`, `OnSync` | syncer 配置。 | +| `PeriodicSyncer` | 周期构造体 | 周期性收敛配置文件。 | +| `SyncResult` | `Clients`, `Error`, `CompletedAt` | 单次同步结果。 | +| `NewPeriodicSyncer` | `NewPeriodicSyncer(opts PeriodicOptions) (*PeriodicSyncer, error)` | 构造 syncer。 | +| `Start` | `Start(ctx context.Context) (func(context.Context) error, error)` | 启动后台循环并返回 stop 闭包。 | +| `DefaultDefinition` / `TCPDefinition` / `XHTTPDefinition` | 模板函数 | 返回预置 Xray 模板。 | +| `VLESSTCPScheme` / `VLESSXHTTPScheme` | URI 模板函数 | 供 sync config / node URL 使用。 | + +### `internal/agentmode`(agent 运行时) + +| 符号 | 签名 / 字段 | 参数 | 返回 | 说明 | +| --- | --- | --- | --- | --- | +| `ClientOptions` | `Timeout`, `InsecureSkipVerify`, `UserAgent`, `AgentID` | N/A | N/A | controller HTTP client 配置。 | +| `Client` | `baseURL`, `token`, `http`, `userAgent`, `agentID` | N/A | N/A | 认证 HTTP client。 | +| `NewClient` | `NewClient(baseURL, token string, opts ClientOptions) (*Client, error)` | controller URL、token、client options | client、error | 构造 controller 客户端。 | +| `ListClients` | `ListClients(ctx context.Context) (agentproto.ClientListResponse, error)` | context | response、error | 调用 `/api/agent-server/v1/users`。 | +| `ReportStatus` | `ReportStatus(ctx context.Context, report agentproto.StatusReport) error` | context、status report | error | 调用 `/api/agent-server/v1/status`。 | +| `Options` | `Logger`, `Agent config.Agent`, `Xray config.Xray` | N/A | N/A | agent mode 总配置。 | +| `Run` | `Run(ctx context.Context, opts Options) error` | context、options | error | 启动 agent 全链路:拉取 clients、生成 config、周期上报状态。 | +| `HTTPClientSource` | `client`, `tracker` | N/A | N/A | 把 controller API 适配成 `xrayconfig.ClientSource`。 | +| `NewHTTPClientSource` | `NewHTTPClientSource(client *Client, tracker *syncTracker) *HTTPClientSource` | client、tracker | source | 构造 HTTP source。 | +| `ListClients`(source) | `ListClients(ctx context.Context) ([]xrayconfig.Client, error)` | context | clients、error | 把 controller payload 转成 syncer 输入。 | + +### `internal/agentserver`(controller 侧 agent registry) + +| 符号 | 签名 / 字段 | 说明 | +| --- | --- | --- | +| `Credential` | `ID`, `Name`, `Token`, `Groups` | 配置层 agent 凭据。 | +| `Config` | `Credentials []Credential` | registry 构造参数。 | +| `Identity` | `ID`, `Name`, `Groups` | 认证成功后的 agent 身份。 | +| `StatusSnapshot` | `Agent Identity`, `Report agentproto.StatusReport`, `UpdatedAt time.Time` | agent 最近一次状态快照。 | +| `Registry` | credential digest、byID、statuses、sandboxAgents、store、logger | controller 内存注册表。 | +| `NewRegistry` | `NewRegistry(cfg Config) (*Registry, error)` | 构造并校验 credential 集。 | +| `SetStore` / `SetLogger` | 注入 store 与 logger | 持久化与日志配置。 | +| `Authenticate` | `Authenticate(token string) (*Identity, bool)` | 通过 token digest 查找 agent identity。 | +| `ReportStatus` | `ReportStatus(agent Identity, report agentproto.StatusReport)` | 更新内存快照并异步持久化到 store。 | +| `RegisterAgent` | `RegisterAgent(agentID string, groups []string) Identity` | 动态注册共享 token 下的 agent。 | +| `Load` | `Load(ctx context.Context) error` | 从 store 回填 agents / statuses。 | +| `Statuses` / `Agents` | 只读导出方法 | 返回当前 registry read model。 | +| `IsSandboxAgent` / `SetSandboxAgent` / `ClearSandboxAgents` | sandbox 标记管理 | 供 sandbox node binding 使用。 | + +### `internal/agentproto`(controller-agent DTO) + +- `type ClientListResponse struct` + - 字段:`Clients []xrayconfig.Client`, `Total int`, `GeneratedAt time.Time`, `Revision string` +- `type StatusReport struct` + - 字段:`AgentID`, `Healthy`, `Message`, `Users`, `SyncRevision`, `Xray XrayStatus` +- `type XrayStatus struct` + - 字段:`Running`, `Clients`, `LastSync`, `ConfigHash`, `NodeID`, `Region`, `LineCode`, `PricingGroup`, `StatsEnabled`, `XrayRevision` + +## English + +### Recommended Reading Order + +Read the current core path in this order: + +1. `cmd/accountsvc/main.go` +2. `api/api.go` +3. `internal/store/store.go` +4. `internal/auth/*` +5. `internal/service/*` +6. `internal/xrayconfig/*` +7. `internal/agentserver/registry.go` +8. `internal/agentmode/*` +9. `internal/agentproto/types.go` + +### `cmd/accountsvc` (runtime composition) + +**Core responsibility** + +- Loads config and chooses runtime mode: `server`, `agent`, or `server-agent`. +- Composes the primary store, GORM admin DB, mailer, token service, OAuth providers, agent registry, and xray syncer. +- Normalizes root / review / sandbox accounts plus RBAC schema. + +**Key non-export owners** + +| Symbol | Purpose | Main inputs | Main outputs | +| --- | --- | --- | --- | +| `rootCmd.RunE` | CLI entry point and mode dispatch | `configPath`, `logLevel` | Calls `runServer`, `runAgent`, or `runServerAndAgent` | +| `runServer(ctx, cfg, logger)` | Main server composition path | `context.Context`, `config.Config`, `*slog.Logger` | Gin server, background loops, injected dependencies | +| `runAgent(ctx, cfg, logger)` | Agent-mode composition path | same | `agentmode.Run(...)` | +| `openAdminSettingsDB(cfg.Store)` | Opens and migrates the GORM-backed admin DB | `config.Store` | `*gorm.DB`, cleanup function | +| `applyRBACSchema(ctx, db, driver)` | Creates and seeds RBAC / users / agents / sessions schema | `context.Context`, `*gorm.DB`, driver name | `error` | + +### `api` (HTTP control layer) + +**Files** + +- `api/api.go`: main route registration plus auth / session / MFA / subscription / OAuth. +- `api/admin_*.go`: admin metrics, agent status, sandbox assume / bind, user operations. +- `api/accounting.go`: account usage / billing / policy / heartbeat reads. +- `api/xworkmate*.go`: tenant, profile, secret, and Vault-backed integration paths. +- `api/stripe.go`: checkout, portal, webhook. +- `api/homepage_video.go`, `api/config_sync.go`, `api/internal_*.go`: auxiliary control-plane endpoints. + +**Core exported symbols** + +- `type Option func(*handler)` +- `WithStore(st store.Store) Option` +- `WithSessionTTL(ttl time.Duration) Option` +- `WithEmailSender(sender EmailSender) Option` +- `WithEmailVerification(enabled bool) Option` +- `WithEmailVerificationTTL(ttl time.Duration) Option` +- `WithUserMetricsProvider(provider service.UserMetricsProvider) Option` +- `WithAgentStatusReader(reader agentStatusReader) Option` +- `WithPasswordResetTTL(ttl time.Duration) Option` +- `WithTokenService(tokenService *auth.TokenService) Option` +- `WithOAuthProviders(providers map[string]auth.OAuthProvider) Option` +- `WithServerPublicURL(url string) Option` +- `WithXrayConfigRenderer(renderer func(*store.User) (string, string, []string, error)) Option` +- `WithOAuthFrontendURL(url string) Option` +- `WithXWorkmateVaultService(vaultService xworkmateVaultService) Option` +- `WithAgentRegistry(registry agentRegistry) Option` +- `WithGormDB(db *gorm.DB) Option` +- `WithStripeConfig(cfg StripeConfig) Option` +- `RegisterRoutes(r *gin.Engine, opts ...Option)` +- `type EmailMessage struct` +- `type EmailSender interface` +- `type EmailSenderFunc` +- `type StripeConfig struct` +- `type XWorkmateVaultConfig struct` +- `NewXWorkmateVaultService(cfg XWorkmateVaultConfig) (xworkmateVaultService, error)` + +**Key non-export owners** + +- `handler`: the HTTP-layer state owner for store, session state, MFA challenges, email verification, password reset, OAuth exchange codes, metrics provider, token service, Vault service, Stripe client, and agent registry. +- `respondError`: emits the standard `{"error":"code","message":"message"}` envelope. +- `sanitizeUser` / `sanitizeSubscription`: stable user and subscription response shapers. +- `buildXWorkmateProfileResponse`: shared response builder for XWorkmate profile / secret APIs. + +### `internal/store` (domain model and persistence abstraction) + +**Key exported types** + +- `User` +- `Subscription` +- `Identity` +- `Agent` +- `TrafficStatCheckpoint` +- `TrafficMinuteBucket` +- `BillingLedgerEntry` +- `AccountQuotaState` +- `AccountBillingProfile` +- `AccountPolicySnapshot` +- `NodeHealthSnapshot` +- `SchedulerDecision` +- `Tenant` +- `TenantDomain` +- `TenantMembership` +- `XWorkmateProfile` +- `XWorkmateSecretLocator` +- `Store` + +**`Store` method groups** + +- User / identity: `CreateUser`, `GetUserByEmail`, `GetUserByID`, `GetUserByName`, `UpdateUser`, `CreateIdentity`, `ListUsers`, `DeleteUser` +- Subscription: `UpsertSubscription`, `ListSubscriptionsByUser`, `CancelSubscription` +- Blacklist: `AddToBlacklist`, `RemoveFromBlacklist`, `IsBlacklisted`, `ListBlacklist` +- Session: `CreateSession`, `GetSession`, `DeleteSession` +- Agent: `UpsertAgent`, `GetAgent`, `ListAgents`, `DeleteAgent`, `DeleteStaleAgents` +- Traffic / billing / scheduler: `UpsertTrafficStatCheckpoint`, `GetTrafficStatCheckpoint`, `ListTrafficStatCheckpoints`, `UpsertTrafficMinuteBucket`, `ListTrafficMinuteBucketsByAccount`, `ListTrafficMinuteBuckets`, `InsertBillingLedgerEntry`, `ListBillingLedgerByAccount`, `UpsertAccountQuotaState`, `GetAccountQuotaState`, `UpsertAccountBillingProfile`, `GetAccountBillingProfile`, `UpsertAccountPolicySnapshot`, `GetLatestAccountPolicySnapshot`, `UpsertNodeHealthSnapshot`, `ListLatestNodeHealthSnapshots`, `InsertSchedulerDecision`, `ListRecentSchedulerDecisions` +- Tenant / XWorkmate: `EnsureTenant`, `EnsureTenantDomain`, `UpsertTenantMembership`, `ResolveTenantByHost`, `ListTenantMembershipsByUser`, `GetTenantMembership`, `GetXWorkmateProfile`, `UpsertXWorkmateProfile` + +**Normalization and role helpers** + +- `NormalizeTenantEdition` +- `NormalizeTenantMembershipRole` +- `NormalizeTenantDomainKind` +- `NormalizeTenantDomainStatus` +- `NormalizeXWorkmateProfileScope` +- `NormalizeXWorkmateSecretLocator` +- `NormalizeHostname` +- `IsSharedTenantHost` +- `GenerateRandomTenantDomain` +- `IsRootRole` +- `IsAdminRole` +- `IsOperatorRole` +- `NewMemoryStore` +- `NewMemoryStoreWithSuperAdminCounting` + +### `internal/auth` (authentication, OAuth, middleware) + +Core exported symbols: + +- `TokenPair` +- `Claims` +- `TokenConfig` +- `TokenService` +- `NewTokenService(config TokenConfig) *TokenService` +- `SetStore(st store.Store)` +- `ValidatePublicToken(publicToken string) bool` +- `GeneratePublicToken(userID, email string, roles []string) string` +- `GenerateTokenPair(userID, email string, roles []string) (*TokenPair, error)` +- `ValidateAccessToken(accessToken string) (*Claims, error)` +- `RefreshAccessToken(refreshToken string) (string, error)` +- `GetAccessTokenExpiry() time.Duration` +- `OAuthUserProfile` +- `OAuthProvider` +- `GitHubProvider` +- `GoogleProvider` +- `NewGitHubProvider(clientID, clientSecret, redirectURL string) *GitHubProvider` +- `NewGoogleProvider(clientID, clientSecret, redirectURL string) *GoogleProvider` +- `AuthMiddleware() gin.HandlerFunc` +- `RequireActiveUser(s store.Store) gin.HandlerFunc` +- `InternalAuthMiddleware() gin.HandlerFunc` +- `RequireMFA() gin.HandlerFunc` +- `RequireRole(role string) gin.HandlerFunc` +- `GetUserID`, `GetEmail`, `GetRoles`, `IsMFAVerified` +- `HTTPHandler` +- `Wrap(handler HTTPHandler) gin.HandlerFunc` + +### `internal/service` (admin-side services) + +Admin settings: + +- `AdminSettings` +- `SetDB` +- `GetAdminSettings` +- `SaveAdminSettings` + +Homepage video: + +- `HomepageVideoEntry` +- `HomepageVideoSettings` +- `GetHomepageVideoSettings` +- `SaveHomepageVideoSettings` +- `ResolveHomepageVideoEntry` + +User metrics: + +- `UserRepository` +- `SubscriptionProvider` +- `UserRecord` +- `SubscriptionState` +- `UserMetricsService` +- `UserMetricsProvider` +- `UserMetrics` +- `MetricsOverview` +- `MetricsSeries` +- `MetricsPoint` +- `Compute(ctx context.Context) (UserMetrics, error)` + +### `internal/xrayconfig` (config generation and sync) + +- `Definition` +- `JSONDefinition` +- `Client` +- `Generator` +- `Generate(clients []Client) error` +- `Render(clients []Client) ([]byte, error)` +- `GormClientSource` +- `NewGormClientSource(db *gorm.DB) (*GormClientSource, error)` +- `ClientSource` +- `PeriodicOptions` +- `PeriodicSyncer` +- `SyncResult` +- `NewPeriodicSyncer(opts PeriodicOptions) (*PeriodicSyncer, error)` +- `Start(ctx context.Context) (func(context.Context) error, error)` +- `DefaultDefinition`, `TCPDefinition`, `XHTTPDefinition` +- `VLESSTCPScheme`, `VLESSXHTTPScheme` + +### `internal/agentmode` (agent runtime) + +- `ClientOptions` +- `Client` +- `NewClient(baseURL, token string, opts ClientOptions) (*Client, error)` +- `ListClients(ctx context.Context) (agentproto.ClientListResponse, error)` +- `ReportStatus(ctx context.Context, report agentproto.StatusReport) error` +- `Options` +- `Run(ctx context.Context, opts Options) error` +- `HTTPClientSource` +- `NewHTTPClientSource(client *Client, tracker *syncTracker) *HTTPClientSource` +- `ListClients(ctx context.Context) ([]xrayconfig.Client, error)` on `HTTPClientSource` + +### `internal/agentserver` (controller-side registry) + +- `Credential` +- `Config` +- `Identity` +- `StatusSnapshot` +- `Registry` +- `NewRegistry(cfg Config) (*Registry, error)` +- `SetStore` +- `SetLogger` +- `Authenticate` +- `ReportStatus` +- `RegisterAgent` +- `Load` +- `Statuses` +- `Agents` +- `IsSandboxAgent` +- `SetSandboxAgent` +- `ClearSandboxAgents` + +### `internal/agentproto` (controller-agent DTOs) + +- `ClientListResponse` +- `StatusReport` +- `XrayStatus` diff --git a/docs/development/testing.md b/docs/development/testing.md index 3a184cd..04ba0bd 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -1,17 +1,95 @@ -# 测试策略 +# Testing Guide / 测试策略 -## 单元测试 +## 中文 + +### 文档校验基线 + +本仓库当前最可靠的文档一致性校验命令是: ```bash -make test -# 或 go test ./... ``` -## 集成测试 +原因: + +- 它直接覆盖当前 Go 模块里的可编译包。 +- 文档中涉及的包名、导出符号、接口签名、请求结构和行为路径都必须至少与这个基线不冲突。 +- 本次工程文档补齐以源码为唯一事实源,因此文档更新完成后应重新执行这条命令确认基线仍为绿色。 + +### 常用命令 ```bash +go test ./... +make test make integration-test ``` -集成测试会执行初始化与创建管理员流程,依赖本地数据库环境。 +说明: + +- `make test` 是仓库对 `go test` 的脚本封装。 +- `make integration-test` 运行 `tests/e2e/superadmin-login/run-test-scripts.sh`,依赖本地数据库与初始化流程。 + +### 当前包级测试面 + +| 包 | 当前状态 | 说明 | +| --- | --- | --- | +| `account/api` | 有测试 | 覆盖注册、登录、MFA、session、sync config、admin settings、xworkmate、accounting、homepage video 等主 API 路径。 | +| `account/internal/agentserver` | 有测试 | 覆盖 registry 构造、认证、状态上报。 | +| `account/internal/mailer` | 有测试 | 覆盖 TLS mode 解析与 sender 构造。 | +| `account/internal/service` | 有测试 | 覆盖 user metrics 聚合行为。 | +| `account/internal/store` | 有测试 | 覆盖 memory / postgres 相关关键行为与 xworkmate 归一化。 | +| `account/internal/xrayconfig` | 有测试 | 覆盖 generator、gorm source、periodic syncer。 | +| `cmd/*`、`internal/auth`、`internal/agentmode`、`internal/syncer`、`internal/utils`、`internal/model`、`config`、`sql` | 暂无测试文件 | 文档应以源码对读为主,并明确哪些行为缺少直接测试保护。 | + +### 文档更新后的核对清单 + +1. `RegisterRoutes` 中出现的 endpoint,文档必须存在且路径、方法、鉴权方式一致。 +2. `internal/store.Store`、`auth.TokenService`、`service.UserMetricsService`、`xrayconfig.PeriodicSyncer` 等关键接口的签名必须与源码逐字对齐。 +3. 对于暂无测试文件的包,文档必须避免推测性表述,只记录源码中可直接确认的行为。 + +## English + +### Documentation Verification Baseline + +The most reliable verification command for documentation alignment in this repository is: + +```bash +go test ./... +``` + +Why this is the baseline: + +- It covers all buildable Go packages in the current module. +- Package names, exported symbols, interface signatures, request structs, and behavior described in the docs must remain compatible with this baseline. +- This documentation refresh uses source code as the only source of truth, so the baseline should be rerun after every doc update. + +### Common Commands + +```bash +go test ./... +make test +make integration-test +``` + +Notes: + +- `make test` is the repository wrapper around Go tests. +- `make integration-test` runs `tests/e2e/superadmin-login/run-test-scripts.sh` and depends on local database setup plus initialization steps. + +### Current Package-Level Test Surface + +| Package | Current status | Notes | +| --- | --- | --- | +| `account/api` | Tested | Covers registration, login, MFA, session, sync config, admin settings, xworkmate, accounting, and homepage video paths. | +| `account/internal/agentserver` | Tested | Covers registry construction, authentication, and status reporting. | +| `account/internal/mailer` | Tested | Covers TLS mode parsing and sender construction. | +| `account/internal/service` | Tested | Covers user metrics aggregation behavior. | +| `account/internal/store` | Tested | Covers key memory / postgres behavior plus xworkmate normalization. | +| `account/internal/xrayconfig` | Tested | Covers generator, GORM source, and periodic syncer behavior. | +| `cmd/*`, `internal/auth`, `internal/agentmode`, `internal/syncer`, `internal/utils`, `internal/model`, `config`, `sql` | No direct test files | Documentation for these packages should stay source-driven and avoid speculative claims. | + +### Post-Update Checklist + +1. Every endpoint registered in `RegisterRoutes` must exist in the docs with matching path, method, and auth mode. +2. Key signatures such as `internal/store.Store`, `auth.TokenService`, `service.UserMetricsService`, and `xrayconfig.PeriodicSyncer` must match the source exactly. +3. For packages without direct tests, the docs must record only behavior that is directly visible in source. diff --git a/docs/en/README.md b/docs/en/README.md index b4031ff..6c3b637 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -1,15 +1,37 @@ # Accounts Service Plus Documentation -This repository is a Go service with API, configuration, runtime operations, and deployment responsibilities. +This documentation set now covers the current engineering reality of `accounts.svc.plus` instead of placeholder summaries. The detailed pages under `docs/architecture/*`, `docs/api/*`, and `docs/development/*` are shared bilingual pages, while `docs/en/*` remains the English entry layer. -## Current state snapshot +## What Is Covered -- Root README title: `accounts.svc.plus` -- Build/runtime evidence: go.mod (`account`) -- Primary directories detected: `cmd/`, `internal/`, `api/`, `accountsvc/`, `deploy/`, `ansible/`, `scripts/`, `tests/`, `sql/`, `config/` -- Existing docs count: 44 +- System design from `cmd/accountsvc/main.go` through API routing, store access, agent registry, and Xray sync. +- Package and type ownership for `api`, `internal/store`, `internal/auth`, `internal/service`, `internal/xrayconfig`, `internal/agentmode`, `internal/agentserver`, and `internal/agentproto`. +- HTTP contracts including request fields, response fields, auth mode, owner handler file, and error conventions. -## Canonical pages +## Recommended Reading Paths + +### For architecture readers + +1. [Architecture](architecture.md) +2. [Detailed startup and runtime flow](../architecture/overview.md) +3. [Component ownership map](../architecture/components.md) +4. [Design decisions](../architecture/design-decisions.md) + +### For API and integration readers + +1. [Developer Guide](developer-guide.md) +2. [API overview](../api/overview.md) +3. [Authentication and authorization](../api/auth.md) +4. [Endpoint matrix](../api/endpoints.md) +5. [Error conventions](../api/errors.md) + +### For codebase readers + +1. [Developer Guide](developer-guide.md) +2. [Code structure reference](../development/code-structure.md) +3. [Testing baseline](../development/testing.md) + +## Canonical Entry Pages - [Architecture](architecture.md) - [Design](design.md) @@ -18,17 +40,7 @@ This repository is a Go service with API, configuration, runtime operations, and - [Developer Guide](developer-guide.md) - [Vibe Coding Reference](vibe-coding-reference.md) -## Legacy docs to fold in +## Notes -- `Runbook/Feature-Sandbox-Mode-and-Sync-Fix.md` -- `Runbook/Fix-Agent-404-And-UUID-Change.md` -- `Runbook/Fix-CloudRun-Stunnel-Startup-Failure.md` -- `Runbook/Fix-Rotating-UUID-Sync-Archive-2026-02-06.md` -- `Runbook/README.md` -- `Runbook/Security-Scrubbing-Archive-2026-02-06.md` -- `SMTP_GMAIL_SETUP.md` -- `advanced/customization.md` -- `advanced/performance.md` -- `advanced/scalability.md` -- `advanced/security.md` -- `api/auth.md` +- Detailed subsystem pages are intentionally shared instead of duplicated into `docs/en/api/*` or `docs/en/architecture/*`. +- The validation baseline for the current docs set is `go test ./...`; see [testing](../development/testing.md). diff --git a/docs/en/architecture.md b/docs/en/architecture.md index c98f273..480cf3d 100644 --- a/docs/en/architecture.md +++ b/docs/en/architecture.md @@ -1,29 +1,37 @@ # Architecture -This repository is a Go service with API, configuration, runtime operations, and deployment responsibilities. +Use this page as the English entry point into the shared bilingual architecture documents. -Use this page as the canonical bilingual overview of system boundaries, major components, and repo ownership. +## Current Architecture In One View -## Current code-aligned notes +`accounts.svc.plus` is a Gin-based Go service that combines: -- Documentation target: `accounts.svc.plus` -- Repo kind: `go-service` -- Manifest and build evidence: go.mod (`account`) -- Primary implementation and ops directories: `cmd/`, `internal/`, `api/`, `accountsvc/`, `deploy/`, `ansible/`, `scripts/`, `tests/`, `sql/`, `config/` -- Package scripts snapshot: No package.json scripts were detected. +- identity and session management, +- admin control-plane operations, +- agent and Xray configuration control, +- and usage / billing read models. -## Existing docs to reconcile +The actual startup chain is centered in `cmd/accountsvc/main.go`, where the service wires the primary `store.Store`, the GORM-backed admin DB, optional mailer and token service, the agent registry, and the optional Xray periodic syncer before calling `api.RegisterRoutes`. -- `api/overview.md` -- `architecture/components.md` -- `architecture/design-decisions.md` -- `architecture/overview.md` -- `architecture/roadmap.md` -- `development/code-structure.md` +## Read In This Order -## What this page should cover next +1. [Architecture overview](../architecture/overview.md) + This is the main runtime story: startup, request flow, session ownership, agent reporting, and Xray config generation. +2. [Components](../architecture/components.md) + Use this when you need the ownership map by package and dependency direction. +3. [Design decisions](../architecture/design-decisions.md) + Use this when you need the current tradeoffs, not just the shape of the system. -- Describe the current implementation rather than an aspirational future-only design. -- Keep terminology aligned with the repository root README, manifests, and actual directories. -- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above. -- Keep diagrams and ownership notes synchronized with actual directories, services, and integration dependencies. +## Key Architecture Themes + +- Session-first control plane with optional JWT middleware. +- `store.Store` as the core business persistence abstraction. +- Selective GORM usage for admin settings, homepage video, sandbox binding, and tenant / XWorkmate models. +- Agent status and sandbox bindings projected into `agentserver.Registry`. +- Xray config generated from database state through `xrayconfig.Generator` and optionally converged by `PeriodicSyncer`. + +## Related Pages + +- [Design](design.md) +- [Developer Guide](developer-guide.md) +- [API overview](../api/overview.md) diff --git a/docs/en/design.md b/docs/en/design.md index b2620d4..703bc0a 100644 --- a/docs/en/design.md +++ b/docs/en/design.md @@ -1,24 +1,32 @@ # Design -This repository is a Go service with API, configuration, runtime operations, and deployment responsibilities. +Use this page as the English entry point for the current design tradeoffs behind `accounts.svc.plus`. -Use this page to consolidate design decisions, ADR-style tradeoffs, and roadmap-sensitive implementation notes. +## Primary Decision Record -## Current code-aligned notes +The main design record is [architecture/design-decisions.md](../architecture/design-decisions.md). It captures the implementation choices that are actually true in the current codebase, including: -- Documentation target: `accounts.svc.plus` -- Repo kind: `go-service` -- Manifest and build evidence: go.mod (`account`) -- Primary implementation and ops directories: `cmd/`, `internal/`, `api/`, `accountsvc/`, `deploy/`, `ansible/`, `scripts/`, `tests/`, `sql/`, `config/` -- Package scripts snapshot: No package.json scripts were detected. +- session-first authentication instead of a JWT-only control plane, +- `store.Store` as the boundary around primary business persistence, +- GORM used only for specific admin-side models, +- agent pre-shared token authentication through `agentserver.Registry`, +- XWorkmate secret locator plus Vault-backed secret persistence, +- Xray config generation and periodic convergence as the control model. -## Existing docs to reconcile +## Suggested Reading Order -- `architecture/design-decisions.md` +1. [Design decisions](../architecture/design-decisions.md) +2. [Architecture overview](../architecture/overview.md) +3. [Authentication details](../api/auth.md) -## What this page should cover next +## Design Snapshot -- Describe the current implementation rather than an aspirational future-only design. -- Keep terminology aligned with the repository root README, manifests, and actual directories. -- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above. -- Promote one-off implementation notes into reusable design records when behavior, APIs, or deployment contracts change. +- Runtime truth is meant to come from the current store / runtime contracts, not from duplicated local config-center state. +- Session, MFA challenge, email verification, password reset, and OAuth exchange state are process-local by design in the current implementation. +- Admin policy and homepage customization are intentionally separated into GORM-backed services rather than folded into the primary store abstraction. +- Agent mode reuses the same Xray generation primitives as server mode instead of introducing a second configuration model. + +## Related Pages + +- [Architecture](architecture.md) +- [Developer Guide](developer-guide.md) diff --git a/docs/en/developer-guide.md b/docs/en/developer-guide.md index 251bc02..68648e6 100644 --- a/docs/en/developer-guide.md +++ b/docs/en/developer-guide.md @@ -1,31 +1,42 @@ # Developer Guide -This repository is a Go service with API, configuration, runtime operations, and deployment responsibilities. +Use this page as the English navigation layer for the shared bilingual engineering references. -Use this page to document local setup, project structure, test surfaces, and contribution conventions tied to the current codebase. +## Start Here -## Current code-aligned notes +1. [Code structure reference](../development/code-structure.md) +2. [API overview](../api/overview.md) +3. [Authentication and authorization](../api/auth.md) +4. [Endpoint matrix](../api/endpoints.md) +5. [Error conventions](../api/errors.md) +6. [Testing baseline](../development/testing.md) -- Documentation target: `accounts.svc.plus` -- Repo kind: `go-service` -- Manifest and build evidence: go.mod (`account`) -- Primary implementation and ops directories: `cmd/`, `internal/`, `api/`, `accountsvc/`, `deploy/`, `ansible/`, `scripts/`, `tests/`, `sql/`, `config/` -- Package scripts snapshot: No package.json scripts were detected. +## What Each Detailed Page Answers -## Existing docs to reconcile +- [Code structure reference](../development/code-structure.md) + Which package owns what, which exported types matter, which non-exported owners carry runtime behavior, and how the core packages connect. +- [API overview](../api/overview.md) + How route families are organized, where handlers live, and how authentication layers stack. +- [Authentication and authorization](../api/auth.md) + Request and response fields for session login, MFA, OAuth exchange, JWT refresh, password reset, and XWorkmate secret flows. +- [Endpoint matrix](../api/endpoints.md) + Method, path, owner file, auth mode, request parameters, response shape, and dependency wiring for the current route set. +- [Testing baseline](../development/testing.md) + The verification baseline for docs and code alignment, centered on `go test ./...`. -- `api/auth.md` -- `api/endpoints.md` -- `api/errors.md` -- `api/overview.md` -- `development/code-structure.md` -- `development/contributing.md` -- `development/dev-setup.md` -- `development/testing.md` +## Validation Baseline -## What this page should cover next +The documentation was aligned against current source, then validated with: -- Describe the current implementation rather than an aspirational future-only design. -- Keep terminology aligned with the repository root README, manifests, and actual directories. -- Link deeper runbooks, specs, or subsystem notes from the legacy docs listed above. -- Keep setup and test commands tied to actual package scripts, Make targets, or language toolchains in this repository. +```bash +go test ./... +``` + +If you update routes, type signatures, or package ownership, update the detailed pages above in the same change. + +## Related Pages + +- [Architecture](architecture.md) +- [Design](design.md) +- [Development setup](../development/dev-setup.md) +- [Contributing](../development/contributing.md) diff --git a/docs/zh/README.md b/docs/zh/README.md index 4f1cba6..63ee46d 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -1,15 +1,37 @@ -# 账号与身份服务 文档 +# 账号与身份服务文档 -该仓库是 Go 服务,文档需要覆盖 API、配置、运行时操作与部署职责。 +当前文档集已经从占位摘要补齐为“实现级工程文档”。详细页统一落在共享双语页面 `docs/architecture/*`、`docs/api/*`、`docs/development/*`,而 `docs/zh/*` 继续承担中文入口页角色。 -## 当前状态快照 +## 当前覆盖范围 -- 根 README 标题: `accounts.svc.plus` -- 构建与运行时证据: go.mod (`account`) -- 自动识别的主要目录: `cmd/`, `internal/`, `api/`, `accountsvc/`, `deploy/`, `ansible/`, `scripts/`, `tests/`, `sql/`, `config/` -- 现有文档数量: 44 +- 从 `cmd/accountsvc/main.go` 出发的系统设计、启动链路、agent registry、Xray sync 与运行时边界。 +- `api`、`internal/store`、`internal/auth`、`internal/service`、`internal/xrayconfig`、`internal/agentmode`、`internal/agentserver`、`internal/agentproto` 的包级职责与类型所有权。 +- HTTP 接口的请求字段、返回字段、鉴权方式、owner handler file、错误约定与依赖对象。 -## 核心双语文档 +## 推荐阅读路径 + +### 如果你想先看架构 + +1. [架构](architecture.md) +2. [启动与运行时主链路](../architecture/overview.md) +3. [组件职责图](../architecture/components.md) +4. [设计决策](../architecture/design-decisions.md) + +### 如果你想先看 API 与接口契约 + +1. [开发手册](developer-guide.md) +2. [API 总览](../api/overview.md) +3. [认证与鉴权](../api/auth.md) +4. [接口矩阵](../api/endpoints.md) +5. [错误约定](../api/errors.md) + +### 如果你想先看代码结构 + +1. [开发手册](developer-guide.md) +2. [代码结构参考](../development/code-structure.md) +3. [测试基线](../development/testing.md) + +## 核心入口页 - [架构](architecture.md) - [设计](design.md) @@ -18,17 +40,7 @@ - [开发手册](developer-guide.md) - [Vibe Coding 参考](vibe-coding-reference.md) -## 待归并的历史文档 +## 说明 -- `Runbook/Feature-Sandbox-Mode-and-Sync-Fix.md` -- `Runbook/Fix-Agent-404-And-UUID-Change.md` -- `Runbook/Fix-CloudRun-Stunnel-Startup-Failure.md` -- `Runbook/Fix-Rotating-UUID-Sync-Archive-2026-02-06.md` -- `Runbook/README.md` -- `Runbook/Security-Scrubbing-Archive-2026-02-06.md` -- `SMTP_GMAIL_SETUP.md` -- `advanced/customization.md` -- `advanced/performance.md` -- `advanced/scalability.md` -- `advanced/security.md` -- `api/auth.md` +- 详细子系统页不再拆分为 `docs/zh/api/*` 之类的新目录,而是集中维护在共享双语细页中。 +- 当前文档与源码一致性的验证基线是 `go test ./...`,详见 [测试基线](../development/testing.md)。 diff --git a/docs/zh/architecture.md b/docs/zh/architecture.md index aeb3397..d8afee9 100644 --- a/docs/zh/architecture.md +++ b/docs/zh/architecture.md @@ -1,29 +1,37 @@ # 架构 -该仓库是 Go 服务,文档需要覆盖 API、配置、运行时操作与部署职责。 +本页是共享双语架构细页的中文入口。 -本页作为系统边界、核心组件与仓库职责的双语总览入口。 +## 当前架构一句话 -## 与当前代码对齐的说明 +`accounts.svc.plus` 是一个以 Gin 为入口的 Go 服务,同时承担: -- 文档目标仓库: `accounts.svc.plus` -- 仓库类型: `go-service` -- 构建与运行依据: go.mod (`account`) -- 主要实现与运维目录: `cmd/`, `internal/`, `api/`, `accountsvc/`, `deploy/`, `ansible/`, `scripts/`, `tests/`, `sql/`, `config/` -- `package.json` 脚本快照: No package.json scripts were detected. +- 账号与会话管理, +- 管理员控制面, +- agent / Xray 配置控制, +- 使用量与计费读面。 -## 需要继续归并的现有文档 +真实启动链路集中在 `cmd/accountsvc/main.go`:主 `store.Store`、GORM 管理面 DB、可选 mailer、可选 token service、agent registry、可选 Xray periodic syncer 都在这里装配完成,然后统一进入 `api.RegisterRoutes`。 -- `api/overview.md` -- `architecture/components.md` -- `architecture/design-decisions.md` -- `architecture/overview.md` -- `architecture/roadmap.md` -- `development/code-structure.md` +## 建议阅读顺序 -## 本页下一步应补充的内容 +1. [架构总览](../architecture/overview.md) + 看主启动链路、请求路径、session 状态持有、agent 上报与 Xray 配置生成。 +2. [组件职责图](../architecture/components.md) + 看按包划分的 owning responsibility、输入输出与依赖方向。 +3. [设计决策](../architecture/design-decisions.md) + 看当前真实取舍,而不是只看结构。 -- 先描述当前已落地实现,再补充未来规划,避免只写愿景不写现状。 -- 术语需要与仓库根 README、构建清单和实际目录保持一致。 -- 将上方列出的历史 runbook、spec、子系统说明逐步链接并归并到本页。 -- 随着目录结构、服务关系和集成依赖变化,持续同步图示与职责说明。 +## 当前架构主题 + +- Session first,JWT optional。 +- `store.Store` 是主业务持久化抽象边界。 +- GORM 只用于 admin settings、homepage video、sandbox binding、tenant / XWorkmate 模型。 +- `agentserver.Registry` 负责把 agent 凭据、身份和状态投影成控制面读视图。 +- Xray 配置以 `xrayconfig.Generator` + `PeriodicSyncer` 为核心收敛模型。 + +## 相关页面 + +- [设计](design.md) +- [开发手册](developer-guide.md) +- [API 总览](../api/overview.md) diff --git a/docs/zh/design.md b/docs/zh/design.md index 6075f08..3e7128a 100644 --- a/docs/zh/design.md +++ b/docs/zh/design.md @@ -1,24 +1,32 @@ # 设计 -该仓库是 Go 服务,文档需要覆盖 API、配置、运行时操作与部署职责。 +本页是 `accounts.svc.plus` 当前设计取舍的中文入口。 -本页用于汇总设计决策、类似 ADR 的权衡记录,以及与路线图相关的实现说明。 +## 主设计记录 -## 与当前代码对齐的说明 +当前最重要的设计记录是 [architecture/design-decisions.md](../architecture/design-decisions.md)。其中固化了当前代码库真实成立的取舍,包括: -- 文档目标仓库: `accounts.svc.plus` -- 仓库类型: `go-service` -- 构建与运行依据: go.mod (`account`) -- 主要实现与运维目录: `cmd/`, `internal/`, `api/`, `accountsvc/`, `deploy/`, `ansible/`, `scripts/`, `tests/`, `sql/`, `config/` -- `package.json` 脚本快照: No package.json scripts were detected. +- 控制面采用 session-first,而不是 JWT-only。 +- 主业务持久化通过 `store.Store` 抽象边界统一暴露。 +- GORM 仅用于部分管理面模型,而不是替代整个 store。 +- agent 侧通过 `agentserver.Registry` 做预共享 token 认证与状态聚合。 +- XWorkmate 采用 secret locator + Vault-backed secret 持久化方案。 +- Xray 配置采用 generator + periodic sync 的生成式控制模型。 -## 需要继续归并的现有文档 +## 建议阅读顺序 -- `architecture/design-decisions.md` +1. [设计决策](../architecture/design-decisions.md) +2. [架构总览](../architecture/overview.md) +3. [认证与鉴权](../api/auth.md) -## 本页下一步应补充的内容 +## 当前设计摘要 -- 先描述当前已落地实现,再补充未来规划,避免只写愿景不写现状。 -- 术语需要与仓库根 README、构建清单和实际目录保持一致。 -- 将上方列出的历史 runbook、spec、子系统说明逐步链接并归并到本页。 -- 当行为、API 或部署契约发生变化时,把一次性实现笔记提升为可复用设计记录。 +- 运行时真相优先来自 store 与运行时契约,而不是本地 config-center 的重复状态。 +- session、MFA challenge、邮箱验证码、密码重置 token、OAuth exchange code 目前都是进程内状态。 +- 管理员权限矩阵和首页视频配置被有意拆到 GORM-backed service,而不是塞进主业务 store。 +- agent mode 与 server mode 复用同一套 Xray 生成能力,而不是引入第二套配置模型。 + +## 相关页面 + +- [架构](architecture.md) +- [开发手册](developer-guide.md) diff --git a/docs/zh/developer-guide.md b/docs/zh/developer-guide.md index be77a99..d518422 100644 --- a/docs/zh/developer-guide.md +++ b/docs/zh/developer-guide.md @@ -1,31 +1,42 @@ # 开发手册 -该仓库是 Go 服务,文档需要覆盖 API、配置、运行时操作与部署职责。 +本页是共享双语工程参考页的中文导航层。 -本页用于记录本地开发环境、项目结构、测试面与贴合当前代码库的贡献约定。 +## 建议起步顺序 -## 与当前代码对齐的说明 +1. [代码结构参考](../development/code-structure.md) +2. [API 总览](../api/overview.md) +3. [认证与鉴权](../api/auth.md) +4. [接口矩阵](../api/endpoints.md) +5. [错误约定](../api/errors.md) +6. [测试基线](../development/testing.md) -- 文档目标仓库: `accounts.svc.plus` -- 仓库类型: `go-service` -- 构建与运行依据: go.mod (`account`) -- 主要实现与运维目录: `cmd/`, `internal/`, `api/`, `accountsvc/`, `deploy/`, `ansible/`, `scripts/`, `tests/`, `sql/`, `config/` -- `package.json` 脚本快照: No package.json scripts were detected. +## 每个细页回答什么问题 -## 需要继续归并的现有文档 +- [代码结构参考](../development/code-structure.md) + 看核心包由谁负责、哪些导出类型重要、哪些非导出 owner 承担主流程,以及各包之间如何连接。 +- [API 总览](../api/overview.md) + 看路由族如何划分、handler 分布在哪些文件里、认证层是怎样叠加的。 +- [认证与鉴权](../api/auth.md) + 看 session 登录、MFA、OAuth exchange、JWT refresh、密码重置、XWorkmate secret API 的请求/返回字段。 +- [接口矩阵](../api/endpoints.md) + 看方法、路径、owner file、鉴权方式、请求参数、返回形状与主要依赖对象。 +- [测试基线](../development/testing.md) + 看当前文档与代码一致性的校验方式,核心基线是 `go test ./...`。 -- `api/auth.md` -- `api/endpoints.md` -- `api/errors.md` -- `api/overview.md` -- `development/code-structure.md` -- `development/contributing.md` -- `development/dev-setup.md` -- `development/testing.md` +## 校验基线 -## 本页下一步应补充的内容 +本轮文档补齐以源码为事实源,并通过以下命令校验: -- 先描述当前已落地实现,再补充未来规划,避免只写愿景不写现状。 -- 术语需要与仓库根 README、构建清单和实际目录保持一致。 -- 将上方列出的历史 runbook、spec、子系统说明逐步链接并归并到本页。 -- 持续让环境搭建与测试命令对应真实存在的脚本、Make 目标或语言工具链。 +```bash +go test ./... +``` + +如果你修改了路由、类型签名、handler 归属或主依赖对象,应同步更新上面的细页。 + +## 相关页面 + +- [架构](architecture.md) +- [设计](design.md) +- [环境搭建](../development/dev-setup.md) +- [贡献约定](../development/contributing.md)