GitHub App 基盤 仕様書
ステータス: Draft / 作成日: 2026-05-27 PR #9a — 依存: なし(
projects+usersテーブルのみ使用)タスクコア(PR #1)と並行実装可。タスクリンク機能は PR #9b で追加する。
1. 概要
GitHub Apps(個人 OAuth トークンではない)を使い、プロジェクトと GitHub リポジトリを接続する基盤。
インストールベースのため token が失効しにくく、org 単位での権限管理ができる。
本 PR の責務:
- GitHub App インストール・コールバック処理
- Installation Access Token の取得・暗号化保存・自動更新
- GitHub Webhook 受信エンドポイント(署名検証のみ。タスクリンク処理は PR #9b)
2. データモデル
github_integrations
3. マイグレーション
CREATE TABLE github_integrations (
id UUID PRIMARY KEY,
project_id UUID NOT NULL UNIQUE REFERENCES projects(id) ON DELETE CASCADE,
installation_id BIGINT NOT NULL,
repo_owner VARCHAR NOT NULL,
repo_name VARCHAR NOT NULL,
access_token_enc TEXT NOT NULL,
token_expires_at TIMESTAMPTZ NOT NULL,
created_by UUID NOT NULL REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
4. GitHub App セットアップフロー
1. テナントオーナーが設定画面の「GitHub と連携」をクリック
→ フロントが GET /v1/tenants/{tid}/projects/{pid}/github/install を呼ぶ
2. バックエンドが CSRF 対策のための state を生成・保存:
- state = 32 バイト乱数(URL-safe base64)
- Redis に state をキー、{ tenant_id, project_id, user_id } を値として保存(TTL: 10 分)
→ GitHub App インストール URL へリダイレクト:
https://github.com/apps/{APP_NAME}/installations/new
?state={state}
3. ユーザーがリポジトリを選択してインストール後、GitHub がコールバックへリダイレクト:
GET /v1/github/callback?installation_id=12345&state={state}
4. バックエンドが state を検証:
- Redis から state を取得(存在しない・期限切れ → 400 Bad Request)
- Redis から削除(再利用防止)
- state に紐付く { tenant_id, project_id, user_id } を復元
5. セッションユーザーが state の user_id と一致するか確認(不一致 → 403)
6. installation_id を使って Installation Access Token を取得
7. トークンを AES-256-GCM で暗号化して DB に保存(project_id で github_integrations を UPSERT)
8. 設定完了 → フロントの設定画面(/settings/github)へリダイレクト
なぜ state が必要か: GitHub App インストールの callback は公開エンドポイントのため、攻撃者が細工した
installation_idを含む URL をターゲットユーザーに踏ませて誤連携させる CSRF が成立しうる。state を Redis で管理することで、正規の開始フローを経たリクエストのみを受け付ける。
GET /v1/tenants/{tid}/projects/{pid}/github/install — インストール開始(セッション必須、テナントオーナー限定):
202 Accepted → Location: https://github.com/apps/{APP_NAME}/installations/new?state=xxx
必要な GitHub App 権限:
5. Token 管理
Installation Access Token の有効期限は 1 時間。期限切れを検出したら GitHub API で再取得して DB を上書きする。
API 呼び出し前:
token_expires_at < now() + 5min → GitHub API で再取得 → DB 更新 → 使用
それ以外 → DB の暗号化済みトークンを復号して使用
トークンは常に AES-256-GCM で暗号化して保存し、API レスポンスでは一切返さない。
6. Webhook 受信
POST /v1/github/webhook はタスク機能に依存しない公開エンドポイント。
本 PR での処理(PR #9a):
X-Hub-Signature-256ヘッダーを HMAC-SHA256 で検証 → 不一致は即403X-GitHub-Eventヘッダーでイベント種別を識別installation_idでgithub_integrationsを検索し対応プロジェクトを特定- イベントを Apalis ジョブキューに積む(タスクリンク処理は PR #9b で実装するため、本 PR では受信確認のみ行い即 200 を返す)
PR #9b で追加する処理:
push/pull_request/createイベントに対するタスクリンク生成ロジック
7. API
POST /github/integration リクエスト:
{
"installation_id": 12345,
"repo_owner": "myorg",
"repo_name": "myapp"
}
GET /github/integration レスポンス:
{
"connected": true,
"repo_owner": "myorg",
"repo_name": "myapp",
"connected_at": "2026-05-27T10:00:00Z"
}
8. セキュリティ
9. フロントエンド(Phase B)
GitHub 連携設定画面
/tenants/{tid}/projects/{pid}/settings/github
┌──────────────────────────────────────────────┐
│ GitHub 連携 │
├──────────────────────────────────────────────┤
│ 接続リポジトリ: myorg/myapp [解除] │
│ │
│ ステータス: ✅ 接続済み │
│ │
│ [GitHub App を再インストール] │
└──────────────────────────────────────────────┘