docs: expand bilingual engineering references

This commit is contained in:
Haitao Pan 2026-04-14 16:32:15 +08:00
parent e80c047a26
commit 37bd7ef917
17 changed files with 2313 additions and 359 deletions

View File

@ -1,49 +1,513 @@
# 认证与鉴权
# Authentication And Authorization / 认证与鉴权
## 会话认证(默认)
## 中文
1) 登录 `POST /api/auth/login` 成功后返回:
- `token`:会话 token
- `expiresAt`
- `user`
### 当前认证模型
2) 客户端后续请求携带:
- `Authorization: Bearer <session-token>`
- Cookie `xc_session=<session-token>`
`accounts.svc.plus` 不是“纯 JWT API”而是
## XWorkmate Vault 集成
- 以 session token 为主控制面事实来源。
- 以 `xc_session` cookie 和 `Authorization: Bearer <session-token>` 为主调用方式。
- 在 `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 <token>` | 受信任服务读接口。 |
| Agent token | `/api/agent-server/v1/users` `/api/agent-server/v1/status` | `Authorization: Bearer <token>` | Agent 身份、client 列表拉取、状态上报。 |
当 SMTP 未配置或使用示例域名时,邮箱验证会自动关闭。
### Session issuance paths
## MFATOTP
以下路径会签发新的 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`
- 当前版本多数保护路由仍使用会话 tokenJWT 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 长度至少 8reset 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 <session-token>`,
- 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 <token>` | Trusted service-to-service reads. |
| Agent token | `/api/agent-server/v1/users`, `/api/agent-server/v1/status` | `Authorization: Bearer <token>` | 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`. |

View File

@ -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 <agent-token>`
## 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)。

View File

@ -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.

View File

@ -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 <session-token>` 或 `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 <token>`
- 与环境变量 `INTERNAL_SERVICE_TOKEN` 比较
- 失败时直接返回简化错误 JSON
#### 4. Agent token
`/api/agent-server/v1/users``/api/agent-server/v1/status``agentserver.Registry.Authenticate`
- 读取 `Authorization: Bearer <agent-token>`
- 允许共享 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 <token>`,
- 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 <agent-token>`,
- 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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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`

View File

@ -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.

View File

@ -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).

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)。

View File

@ -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 firstJWT 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)

View File

@ -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)

View File

@ -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)