GitHub App 基盤 仕様書

GitHub Apps インストール・認証情報管理・Webhook 受信インフラ

ステータス: 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

カラム 制約 説明
id UUID PK  
project_id UUID NOT NULL, UNIQUE, FK→projects CASCADE プロジェクト 1 つにつき 1 リポジトリ
installation_id BIGINT NOT NULL GitHub App のインストール ID
repo_owner VARCHAR NOT NULL 例: myorg
repo_name VARCHAR NOT NULL 例: myapp
access_token_enc TEXT NOT NULL AES-256-GCM で暗号化した Installation Access Token
token_expires_at TIMESTAMPTZ NOT NULL 有効期限(1 時間)。期限切れ時に自動再取得
created_by UUID NOT NULL, FK→users  
created_at TIMESTAMPTZ NOT NULL DEFAULT now()  

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 権限:

権限 レベル 用途
Contents Read コミット読み取り
Pull requests Read & Write PR 読み取り + タスク URL コメント追加(PR #9b で使用)
Metadata Read リポジトリ情報
Webhooks push / pull_request / create イベントを受信

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):

  1. X-Hub-Signature-256 ヘッダーを HMAC-SHA256 で検証 → 不一致は即 403
  2. X-GitHub-Event ヘッダーでイベント種別を識別
  3. installation_idgithub_integrations を検索し対応プロジェクトを特定
  4. イベントを Apalis ジョブキューに積む(タスクリンク処理は PR #9b で実装するため、本 PR では受信確認のみ行い即 200 を返す)

PR #9b で追加する処理:

  • push / pull_request / create イベントに対するタスクリンク生成ロジック

7. API

メソッド パス 説明
GET /v1/tenants/{tid}/projects/{pid}/github/install インストール開始(state 生成・リダイレクト)
GET /v1/github/callback GitHub App インストールコールバック(公開、state 検証あり)
POST /v1/github/webhook GitHub Webhook 受信(公開、署名検証あり)
GET /v1/tenants/{tid}/projects/{pid}/github/integration 連携状態取得(token は返さない)
DELETE /v1/tenants/{tid}/projects/{pid}/github/integration 連携解除

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. セキュリティ

脅威 対策
CSRF(インストール誤連携) state パラメータを Redis で管理(TTL 10 分、コールバック後即削除)。開始ユーザーと callback ユーザーの一致も確認
インストール先プロジェクトの誤連携 state に { tenant_id, project_id } を紐付けて保存。callback 時に復元して UPSERT
Webhook 偽装 X-Hub-Signature-256 を HMAC-SHA256 で検証。不一致は即 403
Installation Access Token の漏洩 DB では AES-256-GCM で暗号化。API レスポンスでは一切返さない
Token 期限切れ token_expires_at を確認し、期限切れなら GitHub API で再取得してから使用

9. フロントエンド(Phase B)

GitHub 連携設定画面

/tenants/{tid}/projects/{pid}/settings/github
┌──────────────────────────────────────────────┐
│ GitHub 連携                                   │
├──────────────────────────────────────────────┤
│ 接続リポジトリ: myorg/myapp          [解除]  │
│                                              │
│ ステータス: ✅ 接続済み                       │
│                                              │
│ [GitHub App を再インストール]                 │
└──────────────────────────────────────────────┘
コンポーネント ファイル
GitHubSettingsPage pages/settings/github/+Page.vue