Skip to Content
流程Installation Flow

GitHub App Installation Flow

详细设计见 openspec/changes/add-connect-repo/design.md。本文是开发期的快速参考。

主流程时序

┌──────────────────┐ ┌───────────────────┐ ┌──────────────────┐ │ Browser │ │ GitHub │ │ NestJS (Fly) │ │ web.douglas-... │ │ github.com │ │ api.douglas-... │ └────────┬─────────┘ └─────────┬─────────┘ └─────────┬────────┘ │ │ │ 1. /projects 空态 → 点「连接代码仓库」 │ │ │ │ │ GET /installations/start ──────────────────────────────▶│ │ │ │ │◀── 302 to github.com/apps/<slug>/installations/new?state=... │ (写 __Host-install_state cookie) │ │ │ │ 2. 用户在 GitHub 选 org / 选 repo / 点 Install ─────────────────▶│ │ │ │ │◀── 302 to api/installations/callback?installation_id=...&state=...&setup_action=install │ │ │ 3. callback ─────────────────────────────────────────────────────▶│ │ │ │ │ │◀── App JWT → GET /app/installations/<id> ─ │ │ (拉 account 元数据) │ │ │ │ │ │◀── installation token → GET /installation/repositories │ │ (per_page=100, 分页拉到 last) │ │ │ │ │ │ ┌─ Postgres ──────┐ │ │ │ │ github_install. │ ◀── upsert │ │ │ │ github_repos │ ◀── upsert │ │ │ └─────────────────┘ │ │◀── 302 web/repos/connect?installation_id=<id> │ │ │ │ 4. /repos/connect 渲染 02b 选择器 → 用户多选 → POST /repos/connect ──────────▶│ │ │ 5. 后续 GitHub 端任何变更(add repo / remove repo / uninstall): GitHub → POST /webhooks/github(带 X-Hub-Signature-256) 后端 HMAC 验签 → 更新 DB ─────────────────────────────────────────────────▶│
Cookie 名用途属性
__Host-oauth_stateuser OAuth state(已有)Path=/、HttpOnly、Secure、SameSite=Lax、Max-Age=600
__Host-install_stateinstallation state(本次新增)同上

两个 cookie 单 host 锁死(api.douglasdong.com),不跨子域。

Webhook 验签

expected = "sha256=" + HMAC_SHA256(rawBody, GITHUB_APP_WEBHOOK_SECRET).hex verify timingSafeEqual(expected, X-Hub-Signature-256 header)

关键约束:必须用 req.rawBody(NestFactory 加 { rawBody: true }),不能用 parsed body 再序列化 —— 字节级不一致签名必然失败。

错误码

?error=来源含义
invalid_install_state/installations/callbackstate cookie 与 query 不匹配,或 setup_action 异常
install_failed/installations/callback调 GitHub API 拉元数据/repos 失败
repos_unavailable/repos/connect 页 SSR/repos 不返回 200,向 /projects 回退
pending_request=1/repos/connect 页 SSR用户没权限直接装 App,向 org admin 发了 install request

Installation token

  • @octokit/auth-appcreateAppAuth({...})({ type: 'installation', installationId })
  • 进程内 Map 缓存(key: installation_id);过期前 5min 触发刷新
  • token 在 GitHub 那边寿命 1 小时
  • 多实例部署后需把缓存换成 Redis(当前 Fly 单实例不用考虑)

数据流:UI → API → DB

ConnectRepoCard.tsx │ on toggle on submit ▼ ▼ local Set<id> POST /repos/connect ReposService.markConnected ├── assertOwnership(防越权) └── UPDATE github_repos SET connected_at = now() WHERE id IN (...) AND connected_at IS NULL