Base Account / Integration

账号系统接入文档

按能力模块拆分的接入规范,供产品工程和 AI Agent 按需获取。

Issuerhttps://user.stringzhao.lifeAudiencebase-account-clientJWKShttps://user.stringzhao.life/.well-known/jwks.jsonDocv2026-03-06.3

Machine Readable Spec

复制 Machine Readable JSON

AI Agent 可直接读取下方 JSON 进行自动接入。结构版本字段为 docVersion。 包含认证系统和邀请码系统的完整规范。

展开查看 Machine Readable JSON
{
  "docVersion": "2026-03-06.3",
  "generatedAt": "2026-03-06.3",
  "service": "base-account-auth",
  "issuer": "https://user.stringzhao.life",
  "audience": "base-account-client",
  "jwksUrl": "https://user.stringzhao.life/.well-known/jwks.json",
  "authorizeContract": {
    "entryPath": "/authorize",
    "requiredQuery": [
      "return_to",
      "state"
    ],
    "optionalQuery": [
      "service (deprecated)"
    ],
    "callbackQuery": [
      "authorized",
      "state"
    ]
  },
  "endpoints": [
    {
      "method": "GET",
      "path": "/authorize",
      "auth": "none",
      "purpose": "统一授权入口。后端基于 return_to 的 origin 识别服务,未登录跳转登录页,已登录则按 consent 状态决定是否直接回跳。可选参数 prompt=select_account:即使已授权,也强制显示账号选择界面(用于多账号切换场景)。",
      "errors": [
        "400 invalid_authorize_request",
        "400 invalid_return_to",
        "400 invalid_service",
        "400 invalid_state"
      ]
    },
    {
      "method": "POST",
      "path": "/api/auth/send-code",
      "auth": "none",
      "purpose": "发送邮箱验证码,返回 requestId。",
      "errors": [
        "400 invalid_input",
        "429 rate_limited",
        "502 email_delivery_failed"
      ]
    },
    {
      "method": "POST",
      "path": "/api/auth/verify-code",
      "auth": "none",
      "purpose": "校验验证码并签发 access/refresh token(同时写入 cookie)。",
      "errors": [
        "400 invalid_code",
        "429 too_many_attempts",
        "403 account_disabled"
      ]
    },
    {
      "method": "POST",
      "path": "/api/auth/authorize/approve",
      "auth": "access_token",
      "purpose": "用户在首次授权页点击同意后写入 consent 记录并返回回跳地址。",
      "errors": [
        "401 missing_access_token",
        "400 invalid_input",
        "400 invalid_return_to",
        "400 invalid_service"
      ]
    },
    {
      "method": "POST",
      "path": "/api/auth/refresh",
      "auth": "refresh_token",
      "purpose": "刷新 access token 和 refresh token。",
      "errors": [
        "401 invalid_refresh_token",
        "400 missing_refresh_token"
      ]
    },
    {
      "method": "POST",
      "path": "/api/auth/logout",
      "auth": "refresh_token",
      "purpose": "注销当前会话并清理 cookie。",
      "errors": [
        "200 幂等,refresh token 失效也可安全调用"
      ]
    },
    {
      "method": "GET",
      "path": "/api/auth/me",
      "auth": "access_token",
      "purpose": "获取当前 access token 对应用户信息。",
      "errors": [
        "401 missing_access_token",
        "401 invalid_access_token"
      ]
    },
    {
      "method": "GET",
      "path": "/.well-known/jwks.json",
      "auth": "none",
      "purpose": "下游服务用于验证 JWT 的公钥集合。",
      "errors": [
        "必须公网可访问,且与 AUTH_ISSUER 保持同源策略"
      ]
    }
  ],
  "integrationSteps": [
    {
      "title": "准备环境变量",
      "detail": "下游服务至少配置 AUTH_ISSUER、AUTH_AUDIENCE、AUTH_JWKS_URL;账号中心配置 AUTH_ALLOWED_RETURN_ORIGINS、AUTH_ALLOWED_RETURN_SUFFIXES。"
    },
    {
      "title": "接入统一授权入口",
      "detail": "外部服务统一跳转 /authorize?return_to&state(service 可传但会被忽略),禁止直接跳 /login。"
    },
    {
      "title": "登记服务域名",
      "detail": "推荐通过 CLI 注册服务:ba admin services create --origin https://your-app.example.com(也可在 Admin Console 的 Services 区域手动登记)。需先登记/启用 origin,再让外部服务发起授权。"
    },
    {
      "title": "处理回跳并建立应用会话",
      "detail": "回跳后在服务端读取共享 access_token cookie 验签 JWT,提取用户 email,然后创建应用自有的 gateway session cookie(HMAC 签名的 email + 过期时间)。重要:不要直接依赖共享 access_token cookie 作为日常登录态,否则跨应用切换账号会导致身份污染。"
    },
    {
      "title": "按需接入 JWT 校验",
      "detail": "如果你的服务需要在后端验签 access token,再接入 auth-sdk 的 JWKS 验签。"
    }
  ],
  "templates": [
    {
      "id": "node-middleware",
      "title": "Node / Express 鉴权中间件",
      "runtime": "Node 20+ / Express"
    },
    {
      "id": "next-route-handler",
      "title": "Next.js Route Handler 保护接口",
      "runtime": "Next.js App Router"
    },
    {
      "id": "frontend-login-flow",
      "title": "前端最小登录流程",
      "runtime": "Browser / SPA"
    },
    {
      "id": "frontend-authorize-entry",
      "title": "外部服务统一授权入口",
      "runtime": "Browser / Web App"
    },
    {
      "id": "frontend-authorize-callback",
      "title": "回跳校验 + 获取用户态(简易版)",
      "runtime": "Browser / Web App"
    },
    {
      "id": "next-gateway-session",
      "title": "Next.js Gateway Session 模式(推荐)",
      "runtime": "Next.js App Router"
    },
    {
      "id": "account-switching",
      "title": "账号切换(多账号场景)",
      "runtime": "Browser / Web App"
    }
  ],
  "checklist": [
    "AUTH_ISSUER 与账号服务域名保持一致(当前: https://user.stringzhao.life)。",
    "AUTH_AUDIENCE 在账号服务和下游服务严格一致(当前: base-account-client)。",
    "AUTH_JWKS_URL 配置为 https://user.stringzhao.life/.well-known/jwks.json。",
    "新接入服务需要先登记并启用 origin。推荐使用 CLI:ba admin services create --origin <url>(也可在 Admin Console -> Services 手动操作)。",
    "/authorize 的 service 参数已弃用(兼容保留,但后端不再依赖该参数判定服务)。",
    "AUTH_ALLOWED_RETURN_ORIGINS 建议至少包含 http://localhost:3000, https://user.stringzhao.life, https://stringzhao.life。",
    "AUTH_ALLOWED_RETURN_SUFFIXES 建议配置为 .stringzhao.life,.vercel.app(一次覆盖你全部 Vercel 服务)。",
    "外部服务统一从 /authorize 进入登录授权流程,不直接拼接 /login。",
    "业务接口对 401/403/429 做显式处理,不把鉴权失败当系统异常。",
    "access_token / refresh_token cookie 在 .stringzhao.life 域共享,任一子域的登录/切换会覆盖所有子域的登录态。接入方应创建应用自有的 gateway session cookie(参考模板),避免跨应用账号污染。",
    "上线后至少做一次 send-code / verify-code / me 全链路回归。"
  ],
  "externalIntegrationChecklist": [
    "授权入口统一改为 /authorize?return_to=<absolute_url>&state=<opaque_state>。",
    "service 参数可传可不传(兼容保留),但不能再用于服务身份判定。",
    "发起授权前生成并持久化 state(建议 randomUUID + sessionStorage)。",
    "回跳后必须校验 authorized=1 且 returned state 与本地 state 完全一致。",
    "每个业务回跳域名(return_to origin)需先开通并启用。推荐使用 CLI:ba admin services create --origin <url>(也可在 /admin -> Services 手动操作)。",
    "回跳后在服务端读取共享 access_token cookie 并验签 JWT(避免前端 CORS),然后创建应用自有的 gateway session cookie 作为日常登录态。不建议直接依赖共享 access_token cookie(跨应用账号污染风险)。",
    "后端 JWT 验签配置保持一致:AUTH_ISSUER、AUTH_AUDIENCE、AUTH_JWKS_URL。",
    "业务侧显式处理 400 invalid_service / 400 invalid_return_to / 401 invalid_access_token。",
    "上线前至少完成首次授权、重复授权直跳、停用服务拦截、icon 展示回退四项回归。",
    "如需账号切换功能,跳转 /authorize 时附加 prompt=select_account 参数。已授权用户将看到账号选择界面,可选择当前账号、历史登录账号或登录新账号。"
  ],
  "invitationCodes": {
    "description": "邀请码系统:每用户每应用可生成 N 个一次性邀请码,兑换后记录邀请关系。",
    "defaultQuota": 3,
    "endpoints": [
      {
        "method": "POST",
        "path": "/api/auth/invitation-codes/generate",
        "auth": "access_token",
        "purpose": "为当前用户在指定应用下生成一个一次性邀请码。每用户每应用有配额限制(普通用户默认 3,管理员 1000)。serviceKey 需先通过 CLI 注册:ba admin services create --origin <url>。",
        "errors": [
          "401 missing_access_token",
          "400 invalid_input",
          "400 invalid_service",
          "403 invitation_quota_exceeded"
        ]
      },
      {
        "method": "POST",
        "path": "/api/auth/invitation-codes/redeem",
        "auth": "access_token",
        "purpose": "兑换邀请码。仅需传入 code,无需 serviceKey(系统自动从邀请码记录中读取)。一次性使用,兑换后记录邀请关系(谁邀请了谁)。普通用户不能兑换自己生成的邀请码;管理员账号可用于自助开通。服务代理模式:使用 API key 认证时可传入 userId 代表终端用户兑换,适用于下游服务有独立 session 的场景。",
        "errors": [
          "401 missing_access_token",
          "400 invalid_invitation_code",
          "400 self_redeem_not_allowed",
          "409 invitation_code_already_redeemed"
        ]
      },
      {
        "method": "POST",
        "path": "/api/auth/invitation-codes/validate",
        "auth": "access_token",
        "purpose": "仅校验邀请码有效性,不消费。仅需传入 code,无需 serviceKey。适用于前端实时校验场景。",
        "errors": [
          "401 missing_access_token",
          "400 invalid_input"
        ]
      },
      {
        "method": "GET",
        "path": "/api/auth/invitation-codes?serviceKey=my-app",
        "auth": "access_token",
        "purpose": "列出当前用户在指定应用下生成的所有邀请码及配额信息。",
        "errors": [
          "401 missing_access_token",
          "400 invalid_input"
        ]
      },
      {
        "method": "POST",
        "path": "/api/auth/invitation-codes/revoke",
        "auth": "access_token",
        "purpose": "撤销自己生成的 ACTIVE 状态邀请码。已兑换的码不可撤销。",
        "errors": [
          "401 missing_access_token",
          "404 invitation_code_not_found",
          "403 forbidden",
          "400 invalid_invitation_code"
        ]
      }
    ],
    "integrationSteps": [
      {
        "title": "生成邀请码",
        "detail": "已登录用户调用 POST /api/auth/invitation-codes/generate,传入 serviceKey,获得 8 位邀请码。每用户每应用默认可生成 3 个(管理员配额为 1000)。serviceKey 需先通过 CLI 注册服务:ba admin services create --origin https://your-app.example.com。"
      },
      {
        "title": "分享邀请码",
        "detail": "将邀请码通过任意渠道(聊天、邮件、社交媒体等)发送给受邀人。"
      },
      {
        "title": "受邀人兑换",
        "detail": "受邀人登录后调用 POST /api/auth/invitation-codes/redeem,仅需传入 code(不需要 serviceKey)。系统自动从邀请码记录中读取 serviceKey,记录邀请关系并返回 serviceKey 和邀请者 ID。"
      },
      {
        "title": "下游业务处理",
        "detail": "下游服务根据 redeem 返回的 serviceKey + creatorId 决定后续动作(如:解锁功能、发放奖励、建立推荐关系等)。"
      }
    ]
  },
  "feedback": {
    "description": "用户反馈系统:Bug/建议/咨询提交 + 双向工单对话,按 serviceKey 隔离来源项目。",
    "types": [
      "BUG",
      "SUGGESTION",
      "INQUIRY"
    ],
    "statuses": [
      "PENDING",
      "IN_PROGRESS",
      "RESOLVED",
      "IGNORED"
    ],
    "userStatusMapping": {
      "IGNORED": "CLOSED"
    },
    "commentVisibility": [
      "INTERNAL",
      "PUBLIC"
    ],
    "commentAuthorRole": [
      "ADMIN",
      "USER"
    ],
    "attachmentLimit": {
      "count": 4,
      "sizeBytes": 2097152,
      "mime": "image/*"
    },
    "rateLimit": {
      "windowSec": 60,
      "maxPerUser": 3
    },
    "endpoints": [
      {
        "method": "POST",
        "path": "/api/feedback",
        "auth": "access_token",
        "purpose": "提交反馈(Bug/建议/咨询),可选附带已上传的截图 url。",
        "errors": [
          "invalid_input(400): 描述为空 / 类型非枚举 / 附件 url 非 https",
          "too_many_attachments(400): 附件数 > 4",
          "invalid_service_key(400): serviceKey 未注册",
          "unauthorized(401): 未登录",
          "rate_limited(429): 单用户 60s 内提交 > 3 条"
        ]
      },
      {
        "method": "GET",
        "path": "/api/feedback/mine",
        "auth": "access_token",
        "purpose": "查看我的反馈列表(含对外消息 messages,仅 PUBLIC;不含内部备注)。",
        "errors": [
          "unauthorized(401): 未登录"
        ]
      },
      {
        "method": "POST",
        "path": "/api/feedback/attachments",
        "auth": "access_token",
        "purpose": "上传单张截图(multipart file),返回 blob url。校验 image/* + ≤2MB。",
        "errors": [
          "invalid_file_type(400): 非 image/*",
          "file_too_large(413): > 2MB",
          "unauthorized(401)"
        ]
      },
      {
        "method": "POST",
        "path": "/api/feedback/:id/messages",
        "auth": "access_token",
        "purpose": "用户在反馈下追加消息(双向对话,通知管理员邮箱)。",
        "errors": [
          "invalid_input(400): 内容为空",
          "forbidden(403): 反馈不属于该用户(横向越权)",
          "feedback_not_found(404)"
        ]
      },
      {
        "method": "GET",
        "path": "/api/admin/feedback",
        "auth": "admin",
        "purpose": "管理员看板列表:按 serviceKey/type/status 筛选 + 描述搜索 + 游标分页。",
        "errors": [
          "forbidden(403): 非管理员"
        ]
      },
      {
        "method": "GET",
        "path": "/api/admin/feedback/:id",
        "auth": "admin",
        "purpose": "管理员详情:反馈全文 + 附件 + 全部 comments(含 INTERNAL 备注 + PUBLIC 消息 + authorRole/visibility)。",
        "errors": [
          "feedback_not_found(404)",
          "forbidden(403): 非管理员"
        ]
      },
      {
        "method": "PATCH",
        "path": "/api/admin/feedback/:id/status",
        "auth": "admin",
        "purpose": "状态流转(PENDING/IN_PROGRESS/RESOLVED/IGNORED)。状态变更不再自动发邮件。",
        "errors": [
          "invalid_status(400): 非法状态值",
          "feedback_not_found(404)",
          "forbidden(403)"
        ]
      },
      {
        "method": "POST",
        "path": "/api/admin/feedback/:id/comments",
        "auth": "admin",
        "purpose": "添加内部备注(visibility=INTERNAL,仅管理员可见,用户看不到)。",
        "errors": [
          "invalid_input(400): 内容为空",
          "feedback_not_found(404)",
          "forbidden(403)"
        ]
      },
      {
        "method": "POST",
        "path": "/api/admin/feedback/:id/reply",
        "auth": "admin",
        "purpose": "回复用户(visibility=PUBLIC,用户可见 + 触发回访邮件,正文含回复内容)。",
        "errors": [
          "invalid_input(400): 内容为空",
          "feedback_not_found(404)",
          "forbidden(403)"
        ]
      }
    ]
  }
}