当前用户
获取当前认证用户的身份信息,以及管理「我的凭证」(API Key、Agent Token、OAuth 授权)。
源码: apps/backend/src/routes/my.ts
GET /my
返回当前认证方式及其关联信息。
认证方式
JWT Token / API Key / Agent Token(combinedAuth)
请求参数
无
响应格式
状态码: 200
响应内容取决于认证方式:
JWT 认证响应
{
"method": "jwt",
"userId": "uuid",
"email": "user@example.com",
"metadata": {
"display_name": "用户名",
"avatar_url": "https://..."
}
}| 字段 | 类型 | 说明 |
|---|---|---|
method | string | 固定值 "jwt" |
userId | string | 用户 UUID |
email | string | 用户邮箱 |
metadata | object | 用户元数据(user_metadata) |
API Key 认证响应
{
"method": "api-key",
"userId": "uuid",
"teamId": "uuid",
"apiKeyId": "uuid"
}| 字段 | 类型 | 说明 |
|---|---|---|
method | string | 固定值 "api-key" |
userId | string | API Key 创建者的用户 ID |
teamId | string | API Key 所属团队 ID |
apiKeyId | string | API Key 记录 ID |
Agent Token 认证响应
{
"method": "agent",
"userId": "uuid",
"teamId": "uuid",
"bindingId": "uuid"
}| 字段 | 类型 | 说明 |
|---|---|---|
method | string | 固定值 "agent" |
userId | string | Agent 关联的用户 ID |
teamId | string | Agent 所属团队 ID |
bindingId | string | agent_bindings 记录 ID |
Agent Token 需在其权限组中显式授予
GET /my访问权限才会到达本端点;未授权时由combinedAuth的checkTokenPermission拦截。
Schedule 认证响应
{
"method": "schedule",
"scheduleId": "uuid",
"teamId": "uuid",
"deploymentId": "uuid"
}| 字段 | 类型 | 说明 |
|---|---|---|
method | string | 固定值 "schedule" |
scheduleId | string | 定时任务 ID |
teamId | string | 所属团队 ID |
deploymentId | string | 目标部署 ID |
请求示例
# JWT 认证
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
https://block2-api.wainao.chat/my
# API Key 认证
curl -H "Authorization: Bearer wn-your-api-key" \
https://block2-api.wainao.chat/my
# Agent Token 认证
curl -H "Authorization: Bearer wna-your-agent-token" \
https://block2-api.wainao.chat/my错误码
| 状态码 | 错误 | 说明 |
|---|---|---|
| 401 | Missing or invalid authorization header | 缺少认证信息 |
| 401 | Invalid API key / Invalid token | 认证失败 |
| 403 | Permission denied: ... | Token 权限组不包含 GET /my |
POST /my/default-project/ensure
幂等创建或返回当前用户的「个人默认项目」。移动端录音、硬件等个人默认数据先调用此端点拿到 project.id,再复用 POST /storage/:projectId/upload 上传。
认证方式
接受:
- JWT Token
- OAuth Access Token(
wno-...),且 token scopes 必须覆盖mobile:full
API Key、Agent Token、Schedule Token 即使认证有效,也会被子路由 guard 拒绝。OAuth 场景下,RPC 的 p_user_id 始终来自 auth.userId,不会读取 body/query 中的用户 ID。
请求体
{
"lang": "zh-CN"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
lang | string | 否 | 命名语言。缺省时使用 Accept-Language 首段,再按 X-Server-Mode 兜底。 |
响应格式
{
"success": true,
"data": {
"team": {
"id": "uuid",
"slug": "team-slug",
"name": "我的团队"
},
"project": {
"id": "uuid",
"team_id": "uuid",
"slug": "personal-space",
"name": "个人空间",
"system_purpose": "personal_default"
},
"permissions": {
"storage_write": true,
"deletion_locked": true
}
}
}行为说明
- 幂等:同一用户重复调用返回同一个 active
personal_default项目。 - 按需创建:不会在登录或
/auth/initialize时预建。 - 归属:项目落在用户本人拥有的 active team 下;不会落到别人邀请用户加入的协作团队。
- 可见性:项目在 Web 项目列表可见,但不可删除、不可设为公开;项目内改名、编辑、上传仍可正常进行。
- 存储:拿到
project.id后,直接调用POST /storage/{project.id}/upload。
错误码
| 状态码 | 错误 | 说明 |
|---|---|---|
| 401 | Missing or invalid authorization header / Invalid token / Invalid or revoked OAuth token / OAuth token has expired | 缺少认证或 token 无效、过期、撤销 |
| 403 | insufficient_scope | OAuth token 有效,但 scope 不包含 mobile:full 对本路由的授权 |
| 403 | This endpoint only accepts JWT or OAuth authentication | 凭证有效但不是 JWT/OAuth(例如 API Key、Agent Token) |
| 409 | personal_default_project_invalid_team | 既有 personal_default 项目挂在非本人 owned team 或已删除 team,需人工修复数据 |
| 500 | internal_error | RPC 调用失败或返回结构异常 |
GET /my/account-bindings
返回当前账号可用于登录/绑定管理的邮箱与手机号状态。微信自动生成的占位邮箱会被过滤,不会作为真实邮箱返回。
认证方式
接受 JWT Token,或带 mobile:full scope 的 OAuth Access Token(wno-...)。API Key、Agent Token、Schedule Token 不允许访问。
响应格式
{
"success": true,
"data": {
"email": "user@example.com",
"email_verified_at": "2026-06-30T00:00:00Z",
"phone": "13800138000",
"phone_verified_at": "2026-06-30T00:00:00Z"
}
}未绑定的字段返回 null。
POST /my/phone/send-otp
已登录用户在绑定手机号前,向目标手机号发送绑定用短信验证码(purpose=bind)。
此端点定义在
apps/backend/src/routes/hardware-sales/phone-auth.ts的phoneBindRoutes,挂载于/my/phone。它与公开的POST /auth/phone/send-otp(purpose=login,未登录登录/注册用)相互独立:验证码按purpose隔离消费,绑定流程必须经此入口取码,否则校验恒为invalid_code。
认证方式
接受 JWT Token,或带 mobile:full scope 的 OAuth Access Token(wno-...)。API Key、Agent Token、Schedule Token 不允许访问。
请求体
{
"phone": "13800138000"
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
phone | string | 是 | 目标手机号 |
响应格式
状态码:200
{
"success": true,
"message": "验证码已发送",
"expiresIn": 300,
"retryAfter": 60
}| 字段 | 类型 | 说明 |
|---|---|---|
success | boolean | 固定 true |
message | string | 提示文案 |
expiresIn | number | 验证码有效期(秒) |
retryAfter | number | 再次发送的冷却时间(秒) |
短信通道
短信由后端短信 Provider 工厂下发,凭据来源按优先级回落:后台「短信验证码服务」DB 凭证组(service_provider:aliyun-pnvs,已启用即权威)→ env 兜底(本地开发:ALIYUN_PNVS_* → ALIYUN_SMS_*)→ Mock(仅开发/测试,不真实下发)。
- 后台凭证组已启用但配置不全 / endpoint 非法 / DB 读取异常时fail-closed(发码失败,绝不回落 env 或 mock 掩盖问题)。
- 凭据均未配置时回落 Mock 并打印一次告警。
- 管理员可通过
GET /admin/services/sms-credential-source查看运行时实际生效的来源。
改后台凭据后,因
getGroupConfig的 5 分钟 TTL,最长 5 分钟内生效,无需重启进程。
限流
- 内存级 IP 限流:每 IP 每分钟最多
HW_PHONE_OTP_RATE_LIMIT_IP_PER_MINUTE次(默认 10),命中返回 429。 - 手机号冷却:同一手机号在冷却窗口内重复请求返回 429,并附
Retry-After响应头。 - IP 每小时限流:同一 IP 每小时发送次数超限返回 429,响应体为
error=rate_limited且含retryAfter字段与Retry-After头(当前不透出具体reason)。
错误码
| 状态码 | 错误 | 说明 |
|---|---|---|
| 400 | invalid_body | 请求体格式无效 |
| 400 | invalid_phone | 手机号格式无效 |
| 401 | unauthorized | 缺少有效登录身份 |
| 429 | rate_limited | 请求过于频繁(含 retryAfter 字段与 Retry-After 头) |
| 502 | send_failed | 验证码下发失败 |
| 500 | internal_error | 服务暂时不可用 |
POST /my/phone/bind
使用 /my/phone/send-otp 获取的绑定验证码,将手机号绑定到当前账号。验证码与登录/注册短信验证码隔离,必须使用 purpose=bind 的验证码。
认证方式
接受 JWT Token,或带 mobile:full scope 的 OAuth Access Token(wno-...)。
请求体
{
"phone": "13800138000",
"code": "123456"
}响应格式
{
"success": true,
"data": {
"phone": "13800138000",
"phoneToken": "..."
}
}错误码
| 状态码 | 错误 | 说明 |
|---|---|---|
| 400 | invalid_body / invalid_phone / invalid_code / code_expired | 请求体、手机号或验证码无效 |
| 401 | unauthorized | 缺少有效登录身份 |
| 409 | phone_taken | 该手机号已被其他账号绑定 |
| 429 | too_many_attempts | 验证码尝试次数过多 |
| 500 | internal_error | 服务暂时不可用 |
DELETE /my/phone
解绑当前账号手机号。如果解绑后账号没有其它可登录方式,返回 409 last_credential,防止用户锁死账号。
认证方式
接受 JWT Token,或带 mobile:full scope 的 OAuth Access Token(wno-...)。
响应格式
{
"success": true
}POST /my/email/send-otp
向目标邮箱发送账号绑定验证码。此验证码写入独立的 account_binding_email_otp_codes 表,不复用登录 OTP。
认证方式
接受 JWT Token,或带 mobile:full scope 的 OAuth Access Token(wno-...)。
请求体
{
"email": "user@example.com",
"lang": "zh-CN"
}lang 可选;缺省时读取 Accept-Language 首段。
响应格式
{
"success": true,
"message": "验证码已发送",
"expiresIn": 300,
"retryAfter": 60
}错误码
| 状态码 | 错误 | 说明 |
|---|---|---|
| 400 | invalid_body / invalid_email | 请求体或邮箱格式无效 |
| 401 | unauthorized | 缺少有效登录身份 |
| 429 | rate_limited | 请求过于频繁 |
| 502 | send_failed | 邮件下发失败 |
| 500 | internal_error | 服务暂时不可用 |
POST /my/email/bind
使用 /my/email/send-otp 获取的验证码,将邮箱绑定到当前账号。
认证方式
接受 JWT Token,或带 mobile:full scope 的 OAuth Access Token(wno-...)。
请求体
{
"email": "user@example.com",
"code": "123456"
}响应格式
{
"success": true,
"data": {
"email": "user@example.com"
}
}错误码
| 状态码 | 错误 | 说明 |
|---|---|---|
| 400 | invalid_body / invalid_email / invalid_code / code_expired | 请求体、邮箱或验证码无效 |
| 401 | unauthorized | 缺少有效登录身份 |
| 409 | email_taken | 该邮箱已被其他账号绑定 |
| 429 | too_many_attempts | 验证码尝试次数过多 |
| 500 | internal_error | 服务暂时不可用 |
DELETE /my/email
解绑当前账号邮箱。如果解绑后账号没有其它可登录方式,返回 409 last_credential,防止用户锁死账号。
认证方式
接受 JWT Token,或带 mobile:full scope 的 OAuth Access Token(wno-...)。
响应格式
{
"success": true
}我的凭证管理(/my/credentials/*)
「我的凭证」管理子路由组,仅接受 JWT 认证。其他认证方式(API Key / Agent Token)通过 combinedAuth 后会被 jwtOnly guard 拦截返回 403;OAuth token 不在 mobile:full 白名单内,会返回 403 insufficient_scope;无效 token 在认证阶段直接 401 拒绝。
设计意图:用户对自己持有的机器凭证(Agent Token、OAuth 授权)的管理能力,必须使用人类身份(JWT)操作,避免机器凭证自我撤销。
API Key 列表与撤销复用既有
/teams/:teamId/api-keys链路,未在此暴露独立 REST 端点。
GET /my/credentials/agent-tokens
列出当前用户持有的 active Agent Token 绑定。
过滤条件: owner_user_id = auth.userId AND token_hash IS NOT NULL AND deleted_at IS NULL,按创建时间倒序。
响应格式
{
"items": [
{
"binding_id": "uuid",
"agent_user_id": "uuid",
"agent_display_name": "我的 Agent",
"agent_avatar_url": "https://...",
"team_id": "uuid",
"team_name": "团队名",
"team_slug": "team-slug",
"can_send": true,
"can_receive": true,
"created_at": "2026-04-25T..."
}
]
}| 字段 | 类型 | 说明 |
|---|---|---|
binding_id | string | agent_bindings 主键,吊销时使用 |
agent_user_id | string | Agent 对应的虚拟用户 ID |
agent_display_name | string | null | Agent 在 profiles 表中的展示名 |
agent_avatar_url | string | null | Agent 头像 URL |
team_id / team_name / team_slug | string | null | Agent 所属团队(可能为空,代表个人 Agent) |
can_send / can_receive | boolean | Agent 当前的双向通信开关 |
created_at | string | Agent 绑定创建时间(ISO 8601) |
DELETE /my/credentials/agent-tokens/:bindingId
吊销指定 Agent Token,写 token_hash = null 保留绑定本体(与 /team-agents/.../revoke-token 行为一致)。
响应格式
成功(首次撤销):
{ "success": true }幂等(已撤销过):
{ "success": true, "alreadyRevoked": true }错误码
| 状态码 | 错误 | 说明 |
|---|---|---|
| 401 | Missing or invalid authorization header / Invalid token | 缺少认证或 token 无效(在 combinedAuth 阶段拦截) |
| 403 | This endpoint only accepts JWT authentication | 凭证有效但非 JWT(API Key / Agent Token 通过 combinedAuth 后由 jwtOnly guard 拒绝) |
| 403 | Forbidden | binding 不属于当前用户 |
| 404 | Agent binding not found | binding 不存在或已软删除 |
| 500 | Failed to revoke agent token | 数据库写入失败 |
GET /my/credentials/agent-bindings-as-agent
被绑视角(2026-06-10 新增):列出哪些团队把当前用户绑定为 Agent(agent_bindings.agent_user_id = auth.userId AND deleted_at IS NULL)。与上面的 agent-tokens(我授权给别人的)互为对称视角。
响应格式
{
"items": [
{
"binding_id": "uuid",
"owner_user_id": "uuid",
"owner_display_name": "授权人昵称",
"team_id": "uuid",
"team_name": "团队名",
"team_slug": "team-slug",
"can_send": true,
"can_receive": true,
"created_at": "2026-06-10T08:00:00.000Z"
}
]
}DELETE /my/credentials/agent-bindings-as-agent/:bindingId
被绑方主动解除 Agent 绑定:软删除整条 binding 并同时置空 token_hash(与团队侧删除 Agent 同语义),解除后该团队不能再通过此绑定调用当前用户。权限校验按 agent_user_id = auth.userId。
响应 200
{ "success": true }错误码
| 状态码 | 错误 | 说明 |
|---|---|---|
| 403 | Forbidden | binding 的被绑方不是当前用户 |
| 404 | Agent binding not found | binding 不存在或已软删除 |
| 500 | Failed to remove agent binding | 数据库写入失败 |
GET /my/credentials/oauth-authorizations
列出当前用户授权过的第三方 OAuth 应用,按 app_id 聚合(仅展示 active token,且应用未被软删除)。
过滤条件: oauth_tokens.user_id = auth.userId AND oauth_tokens.revoked_at IS NULL AND oauth_apps.deleted_at IS NULL
聚合规则: 同一应用的多枚 token 合并为一条;scopes 取并集;authorized_at 取最新;active_token_count 计数。
响应格式
{
"items": [
{
"app_id": "uuid",
"app_name": "我的应用",
"app_logo_url": "https://...",
"scopes": ["documents:read", "runs:write"],
"authorized_at": "2026-04-25T...",
"active_token_count": 1
}
]
}已禁用的应用(
disabled_at IS NOT NULL)仍会展示,让用户感知后主动撤销。已删除的应用(deleted_at IS NOT NULL)不展示。
DELETE /my/credentials/oauth-authorizations/:appId
撤销该应用下当前用户的所有 active token。严格按 user_id + app_id 过滤,不会影响其他用户的授权(与 admin 端的 revoke_all_oauth_tokens_for_app RPC 语义不同)。
响应格式
{ "success": true, "revokedCount": 2 }幂等:若该应用下当前用户已无 active token,返回 revokedCount: 0。
错误码
| 状态码 | 错误 | 说明 |
|---|---|---|
| 401 | Missing or invalid authorization header / Invalid token | 缺少认证或 token 无效(在 combinedAuth 阶段拦截) |
| 403 | This endpoint only accepts JWT authentication | 凭证有效但非 JWT(API Key / Agent Token 通过 combinedAuth 后由 jwtOnly guard 拒绝) |
| 500 | Failed to revoke oauth authorization | 数据库写入失败 |
账号聚合与站点权限
以下两个端点只接受浏览器用户会话 JWT,不接受 API Key、Agent Token 或 OAuth Token。
GET /my/account
聚合返回 Auth 用户、Profile、站点授权和微信绑定状态,供账号设置页一次性加载。
GET /my/site-permissions
返回当前用户在 site 资源上的显式 grants,用于前端权限入口和管理员能力判断。