docs: expand bilingual engineering references
This commit is contained in:
parent
e80c047a26
commit
37bd7ef917
532
docs/api/auth.md
532
docs/api/auth.md
@ -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
|
||||
|
||||
## MFA(TOTP)
|
||||
以下路径会签发新的 session token:
|
||||
|
||||
- 申请 secret:`POST /api/auth/mfa/totp/provision`
|
||||
- 验证并启用:`POST /api/auth/mfa/totp/verify`
|
||||
- 关闭 MFA:`POST /api/auth/mfa/disable`
|
||||
| 入口 | 请求字段 | 成功返回 | 备注 |
|
||||
| --- | --- | --- | --- |
|
||||
| `POST /api/auth/login` | `identifier/account/username/email` + `password`,或邮箱 + `totpCode` | `message`、`token`、`access_token`、`expiresAt`、`expires_in`、`user` | 如果用户已启用 MFA 且未提交验证码,则不会发 session,而是先返回 MFA challenge。 |
|
||||
| `POST /api/auth/register/verify` | `email`、`code` | `message`、`token`、`expiresAt`、`user` 或 `verified=true` | 已创建用户走“验证邮箱并自动登录”;预注册验证码走“仅标记 verified”。 |
|
||||
| `POST /api/auth/password/reset/confirm` | `token`、`password` | `message`、`token`、`expiresAt`、`user` | 重置密码成功后直接进入新会话。 |
|
||||
| `POST /api/auth/mfa/verify` | `mfa_ticket/mfaToken`、`code/totpCode` | `message`、`token`、`access_token`、`expiresAt`、`expires_in`、`user` | 登录后补做 MFA 的完成步骤。 |
|
||||
| `POST /api/auth/mfa/totp/verify` | `token`、`code` | `message`、`token`、`expiresAt`、`user` | 首次启用 TOTP 成功后直接签发 session。 |
|
||||
| `GET /api/auth/oauth/callback/:provider` | query `code` | `307` redirect 到前端 `/login?exchange_code=...` | callback 自身不直接输出 JSON,而是先创建 session,再下发一次性 exchange code。 |
|
||||
|
||||
登录接口在部分场景会返回 `mfaToken`,用于后续验证。
|
||||
### 注册与邮箱验证
|
||||
|
||||
## JWT 令牌服务(可选)
|
||||
#### `POST /api/auth/register/send`
|
||||
|
||||
启用 `auth.enable: true` 后提供:
|
||||
- `POST /api/auth/token/exchange`:使用 OAuth 回调签发的一次性 `exchange_code` 换取真实会话 token
|
||||
- `POST /api/auth/token/refresh`:刷新 access token
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `email` |
|
||||
| 成功返回 | `200 {"message":"verification email sent"}` |
|
||||
| 前置条件 | 邮箱格式合法;不在 blacklist;若邮箱已存在且未验证,则继续发送验证邮件。 |
|
||||
| 失败返回 | `invalid_request`、`invalid_email`、`email_blacklisted`、`smtp_timeout`、`verification_failed`、`email_already_exists`。 |
|
||||
|
||||
注意事项:
|
||||
- `token/exchange` 只接受后端签发的一次性 `exchange_code`,不再接受调用方自报 `user_id/email/roles`
|
||||
- `token/exchange` 返回的 `token`/`access_token` 是同一个真实会话 token,供前端 BFF 写入 `xc_session`
|
||||
- 当前版本多数保护路由仍使用会话 token,JWT refresh 仅保留给 `token/refresh`
|
||||
- 若开启 JWT 中间件,业务逻辑仍可能需要会话 token;因此控制面应优先走会话模型
|
||||
#### `POST /api/auth/register`
|
||||
|
||||
建议:若主要使用会话认证,请将 `auth.enable` 设为 `false`。
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `name`、`email`、`password`、`code` |
|
||||
| 成功返回 | `201 {"message":"registration successful","user":...}` |
|
||||
| 前置条件 | `name` 非空;邮箱合法;密码长度至少 8;若启用邮箱验证则 `code` 必须存在且匹配。 |
|
||||
| 关键副作用 | 创建 `store.User`;自动 upsert 7 天 trial `store.Subscription`。 |
|
||||
| 失败返回 | `name_required`、`missing_credentials`、`invalid_email`、`password_too_short`、`verification_required`、`invalid_code`、`email_already_exists`、`name_already_exists`。 |
|
||||
|
||||
#### `POST /api/auth/register/verify`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `email`、`code` |
|
||||
| 成功返回 A | `200 {"message":"email verified","token":"...","expiresAt":"...","user":...}` |
|
||||
| 成功返回 B | `200 {"message":"verification successful","verified":true}` |
|
||||
| 分支解释 | 若命中已创建用户的 email verification,接口会把用户标记为已验证并直接签发 session;若命中注册前验证码,只做“验证码通过”标记。 |
|
||||
| 失败返回 | `token_in_query`、`invalid_request`、`invalid_code`、`verification_failed`、`session_creation_failed`。 |
|
||||
|
||||
### 登录、MFA challenge 与会话读取
|
||||
|
||||
#### `POST /api/auth/login`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `identifier`、`account`、`username`、`email`、`password`、`totpCode` |
|
||||
| 标准成功返回 | `message`、`token`、`access_token`、`expiresAt`、`expires_in`、`mfaRequired=false`、`user` |
|
||||
| MFA challenge 返回 | `message="mfa required"`、`mfaRequired=true`、`mfaMethod="totp"`、`mfaTicket`、`mfa_ticket`、兼容字段 `mfaToken` |
|
||||
| 真实规则 | 先按 `identifier -> account -> username -> email` 解析登录标识。若用户已启用 MFA 但未提交 `totpCode`,登录不发 session,只发 challenge。 |
|
||||
| 失败返回 | `credentials_in_query`、`invalid_request`、`missing_credentials`、`user_not_found`、`invalid_credentials`、`password_required`、`email_not_verified`、`sandbox_no_login`、`mfa_challenge_creation_failed`。 |
|
||||
|
||||
#### `POST /api/auth/mfa/verify`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `mfa_ticket`、`mfaToken`、`code`、`totpCode`、`method` |
|
||||
| 成功返回 | `message`、`token`、`access_token`、`expiresAt`、`expires_in`、`user` |
|
||||
| 前置条件 | `method` 仅支持 `totp`;ticket 必须存在且未过期;用户已启用 MFA。 |
|
||||
| 失败返回 | `mfa_ticket_required`、`mfa_code_required`、`unsupported_mfa_method`、`invalid_mfa_ticket`、`mfa_not_enabled`、`invalid_mfa_code`。 |
|
||||
|
||||
#### `GET /api/auth/session`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 认证 | 需要有效 session;若启用 token middleware,还会先经过 JWT + active-user 检查。 |
|
||||
| 成功返回 | `{"user": ...}`,其中可能附带 `tenantId`、`tenants`、XWorkmate access 视图。 |
|
||||
| 失败返回 | `session_token_required`、`invalid_session`、`session_user_lookup_failed`、`account_suspended`。 |
|
||||
|
||||
#### `DELETE /api/auth/session`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 认证 | 需要有效 session。 |
|
||||
| 成功返回 | `204 No Content` |
|
||||
| 副作用 | 删除进程内 session,并清空 cookie。 |
|
||||
|
||||
### TOTP 启用、查询与关闭
|
||||
|
||||
#### `POST /api/auth/mfa/totp/provision`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `token`、`issuer`、`account` |
|
||||
| 成功返回 | `secret`、`otpauth_url`、`issuer`、`account`、`mfaToken`、`mfa`、`user` |
|
||||
| 入口模式 | 可使用已有 `mfa token`,也可使用当前 session 自动创建新的 MFA challenge。 |
|
||||
| 失败返回 | `invalid_request`、`invalid_mfa_token`、`mfa_token_required`、`invalid_session`、`mfa_already_enabled`、`read_only_account`、`mfa_secret_generation_failed`、`mfa_setup_failed`。 |
|
||||
|
||||
#### `POST /api/auth/mfa/totp/verify`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `token`、`code` |
|
||||
| 成功返回 | `{"message":"mfa_verified","token":"...","expiresAt":"...","user":...}` |
|
||||
| 特殊失败 | 多次错误会进入 `429 {"error":"mfa_challenge_locked","retryAt":"...","mfaToken":"..."}`。 |
|
||||
| 常见失败 | `mfa_token_required`、`invalid_mfa_token`、`mfa_secret_missing`、`mfa_code_required`、`invalid_mfa_code`、`mfa_update_failed`。 |
|
||||
|
||||
#### `GET /api/auth/mfa/status`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 输入来源 | query `token` / `identifier` / `email`,header `X-MFA-Token`,或 `Authorization` session token。 |
|
||||
| 成功返回 | `enabled`、`mfa`、`user`。若按 identifier 查询且用户不存在,返回 `200 {"mfa_enabled":false}`。 |
|
||||
| 失败返回 | `mfa_status_failed`、`mfa_token_required`。 |
|
||||
|
||||
#### `POST /api/auth/mfa/disable`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 认证 | 当前 session token,优先从 `Authorization` 读取,也接受 query `token`。 |
|
||||
| 成功返回 | `{"message":"mfa_disabled","user":...}` |
|
||||
| 失败返回 | `session_token_required`、`invalid_session`、`mfa_disable_failed`、`mfa_not_enabled`、`read_only_account`。 |
|
||||
|
||||
### OAuth 与 exchange code
|
||||
|
||||
#### `GET /api/auth/oauth/login/:provider`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 路径参数 | `provider`,当前实现支持 `github` 与 `google`。 |
|
||||
| 成功返回 | `307 Temporary Redirect` 到 provider authorization URL。 |
|
||||
| 失败返回 | `provider_not_found`。 |
|
||||
|
||||
#### `GET /api/auth/oauth/callback/:provider`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 输入 | query `code`,可选由 `state` 带回前端 URL。 |
|
||||
| 成功行为 | 交换 provider token,拉取 profile,创建或复用用户,确保 `store.Identity` 绑定,然后签发 session + 一次性 exchange code,最后 `307` 跳转到前端 `/login?exchange_code=...`。 |
|
||||
| 失败返回 | `provider_not_found`、`code_missing`、`oauth_exchange_failed`、`fetch_profile_failed`、`email_missing`、`email_not_verified`、`store_error`、`user_creation_failed`、`session_creation_failed`、`exchange_code_creation_failed`。 |
|
||||
|
||||
#### `POST /api/auth/token/exchange`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `exchange_code` |
|
||||
| 成功返回 | `token`、`access_token`、`token_type`、`expiresAt`、`expires_in`、`user` |
|
||||
| 语义 | 不是按 user claims 自签 token,而是把 callback 阶段暂存的一次性 code 换回真实 session。 |
|
||||
| 失败返回 | `invalid_request`、`invalid_exchange_code`、`session_user_lookup_failed`。 |
|
||||
|
||||
### JWT refresh
|
||||
|
||||
#### `POST /api/auth/token/refresh`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `refresh_token` |
|
||||
| 成功返回 | `access_token`、`token_type="Bearer"`、`expires_in` |
|
||||
| 前置条件 | `h.tokenService != nil`;refresh token 合法且未过期。 |
|
||||
| 失败返回 | `token_service_unavailable`、`invalid_request`、`invalid_refresh_token`。 |
|
||||
|
||||
#### `POST /api/auth/refresh`
|
||||
|
||||
别名路由,行为与 `POST /api/auth/token/refresh` 完全一致。
|
||||
|
||||
### 密码重置
|
||||
|
||||
#### `POST /api/auth/password/reset`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `email` |
|
||||
| 成功返回 | `202 {"message":"if the account exists a reset email will be sent"}` |
|
||||
| 实现说明 | handler 本身按邮箱驱动;但该路由被挂在 authProtected 组下,因此启用 JWT middleware 后会多一层前置认证。 |
|
||||
| 失败返回 | `email_in_query`、`invalid_request`、`email_required`、`password_reset_failed`、`read_only_account`。 |
|
||||
|
||||
#### `POST /api/auth/password/reset/confirm`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | `token`、`password` |
|
||||
| 成功返回 | `message`、`token`、`expiresAt`、`user` |
|
||||
| 前置条件 | password 长度至少 8;reset token 有效;若用户是 demo/read-only account 则拒绝。 |
|
||||
| 失败返回 | `credentials_in_query`、`invalid_request`、`password_too_short`、`invalid_token`、`password_reset_failed`、`read_only_account`、`session_creation_failed`。 |
|
||||
|
||||
### XWorkmate profile 与 Vault-backed secrets
|
||||
|
||||
#### `GET /api/auth/xworkmate/profile`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 成功返回 | `edition`、`tenant`、`membershipRole`、`profileScope`、`canEditIntegrations`、`canManageTenant`、`profile`、`tokenConfigured` |
|
||||
| profile 字段 | `BRIDGE_SERVER_URL`、`bridgeServerOrigin`、`vaultUrl`、`vaultNamespace`、`vaultSecretPath`、`vaultSecretKey`、`secretLocators`、`apisixUrl` |
|
||||
| 失败返回 | `tenant_membership_required`、`tenant_not_found`、`xworkmate_context_failed`、`xworkmate_profile_read_failed`。 |
|
||||
|
||||
#### `GET /api/auth/xworkmate/profile/sync`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 成功返回 | `BRIDGE_SERVER_URL`、`BRIDGE_AUTH_TOKEN` |
|
||||
| 语义 | 给 bridge / desktop sync 使用的精简同步视图,不返回完整 profile。 |
|
||||
| 失败返回 | `tenant_membership_required`、`tenant_not_found`、`bridge_server_url_unavailable`、`bridge_auth_token_unavailable`。 |
|
||||
|
||||
#### `PUT /api/auth/xworkmate/profile`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 请求字段 | 允许直接传 profile payload,或 `{ "profile": {...} }` |
|
||||
| 禁止字段 | raw token/password/api key 不允许持久化;命中时返回 `token_persistence_forbidden`。 |
|
||||
| 成功返回 | 与 `GET /xworkmate/profile` 同形。 |
|
||||
| 失败返回 | `xworkmate_profile_forbidden`、`read_only_account`、`invalid_request`、`token_persistence_forbidden`、`xworkmate_profile_write_failed`。 |
|
||||
|
||||
#### `GET /api/auth/xworkmate/secrets`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 成功返回 | `tenant`、`membershipRole`、`profileScope`、`canEditIntegrations`、`vaultBackendEnabled`、`tokenConfigured`、`secrets` |
|
||||
| `secrets[]` 元素 | `target`、`provider`、`secretPath`、`secretKey`、`configured`、`required` 等元数据;绝不返回 raw secret。 |
|
||||
| 失败返回 | `xworkmate_secret_read_failed`、`xworkmate_context_failed`、`tenant_not_found`。 |
|
||||
|
||||
#### `PUT /api/auth/xworkmate/secrets/:target`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 路径参数 | `target`,例如 bridge auth token 对应的 secret target。 |
|
||||
| 请求字段 | `value` |
|
||||
| 成功返回 | `secret`、`profileScope`、`tokenConfigured` |
|
||||
| 失败返回 | `xworkmate_secret_forbidden`、`read_only_account`、`xworkmate_secret_unknown_target`、`xworkmate_secret_value_required`、`xworkmate_secret_write_failed`、`xworkmate_profile_write_failed`。 |
|
||||
|
||||
#### `DELETE /api/auth/xworkmate/secrets/:target`
|
||||
|
||||
| 项 | 内容 |
|
||||
| --- | --- |
|
||||
| 路径参数 | `target` |
|
||||
| 成功返回 | `secret`、`profileScope`、`tokenConfigured` |
|
||||
| 语义 | 删除 Vault/backend secret,但保留 locator 元数据。 |
|
||||
| 失败返回 | `xworkmate_secret_forbidden`、`read_only_account`、`xworkmate_secret_unknown_target`、`xworkmate_secret_delete_failed`。 |
|
||||
|
||||
## English
|
||||
|
||||
### Current Auth Model
|
||||
|
||||
`accounts.svc.plus` is not a pure JWT API. It is:
|
||||
|
||||
- session-first for the current control plane,
|
||||
- primarily called through the `xc_session` cookie or `Authorization: Bearer <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`. |
|
||||
|
||||
@ -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)。
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)。
|
||||
|
||||
@ -1,29 +1,37 @@
|
||||
# 架构
|
||||
|
||||
该仓库是 Go 服务,文档需要覆盖 API、配置、运行时操作与部署职责。
|
||||
本页是共享双语架构细页的中文入口。
|
||||
|
||||
本页作为系统边界、核心组件与仓库职责的双语总览入口。
|
||||
## 当前架构一句话
|
||||
|
||||
## 与当前代码对齐的说明
|
||||
`accounts.svc.plus` 是一个以 Gin 为入口的 Go 服务,同时承担:
|
||||
|
||||
- 文档目标仓库: `accounts.svc.plus`
|
||||
- 仓库类型: `go-service`
|
||||
- 构建与运行依据: go.mod (`account`)
|
||||
- 主要实现与运维目录: `cmd/`, `internal/`, `api/`, `accountsvc/`, `deploy/`, `ansible/`, `scripts/`, `tests/`, `sql/`, `config/`
|
||||
- `package.json` 脚本快照: No package.json scripts were detected.
|
||||
- 账号与会话管理,
|
||||
- 管理员控制面,
|
||||
- agent / Xray 配置控制,
|
||||
- 使用量与计费读面。
|
||||
|
||||
## 需要继续归并的现有文档
|
||||
真实启动链路集中在 `cmd/accountsvc/main.go`:主 `store.Store`、GORM 管理面 DB、可选 mailer、可选 token service、agent registry、可选 Xray periodic syncer 都在这里装配完成,然后统一进入 `api.RegisterRoutes`。
|
||||
|
||||
- `api/overview.md`
|
||||
- `architecture/components.md`
|
||||
- `architecture/design-decisions.md`
|
||||
- `architecture/overview.md`
|
||||
- `architecture/roadmap.md`
|
||||
- `development/code-structure.md`
|
||||
## 建议阅读顺序
|
||||
|
||||
## 本页下一步应补充的内容
|
||||
1. [架构总览](../architecture/overview.md)
|
||||
看主启动链路、请求路径、session 状态持有、agent 上报与 Xray 配置生成。
|
||||
2. [组件职责图](../architecture/components.md)
|
||||
看按包划分的 owning responsibility、输入输出与依赖方向。
|
||||
3. [设计决策](../architecture/design-decisions.md)
|
||||
看当前真实取舍,而不是只看结构。
|
||||
|
||||
- 先描述当前已落地实现,再补充未来规划,避免只写愿景不写现状。
|
||||
- 术语需要与仓库根 README、构建清单和实际目录保持一致。
|
||||
- 将上方列出的历史 runbook、spec、子系统说明逐步链接并归并到本页。
|
||||
- 随着目录结构、服务关系和集成依赖变化,持续同步图示与职责说明。
|
||||
## 当前架构主题
|
||||
|
||||
- Session first,JWT optional。
|
||||
- `store.Store` 是主业务持久化抽象边界。
|
||||
- GORM 只用于 admin settings、homepage video、sandbox binding、tenant / XWorkmate 模型。
|
||||
- `agentserver.Registry` 负责把 agent 凭据、身份和状态投影成控制面读视图。
|
||||
- Xray 配置以 `xrayconfig.Generator` + `PeriodicSyncer` 为核心收敛模型。
|
||||
|
||||
## 相关页面
|
||||
|
||||
- [设计](design.md)
|
||||
- [开发手册](developer-guide.md)
|
||||
- [API 总览](../api/overview.md)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user