# 機能設計書 137-Instagram認可

## 概要

本ドキュメントは、Chatwootシステムにおける「Instagram認可」機能の設計仕様を記載する。本機能は、Instagram DMチャネルを設定するためのOAuth 2.0認証フローを実装する。

### 本機能の処理概要

Instagram認可機能は、企業がInstagramダイレクトメッセージ（DM）を通じた顧客対応をChatwootで管理するための認証基盤を提供する。Instagram Business Login（OAuth 2.0）を使用してInstagramアカウントへのアクセス権限を取得し、DMの送受信を可能にする。

**業務上の目的・背景**：
Instagramは視覚的なコンテンツを中心としたソーシャルメディアプラットフォームであり、特にB2C企業にとって重要な顧客接点となっている。Chatwootとの連携により、InstagramのDMを他のチャネル（チャット、メール等）と統合的に管理できるようになる。これにより、効率的な顧客対応と、ビジュアルコンテンツを活用したサポートを実現する。

**機能の利用シーン**：
- 新規にInstagram DMチャネルを追加する場合
- 既存のInstagram連携を再認可する場合（トークン期限切れ時）
- Instagramアカウントからのダイレクトメッセージを受信・返信する場合

**主要な処理内容**：
1. Instagram OAuth 2.0認可URL生成とユーザーリダイレクト
2. 認可コードからアクセストークンへの変換
3. 短期トークンから長期トークンへの変換
4. Instagramユーザー情報の取得
5. Instagramチャネルの作成または更新
6. 既存連携の再認可処理

**関連システム・外部連携**：
- Instagram Graph API
- Instagram Business Login（OAuth 2.0）
- oauth2 gem（OAuth 2.0クライアント）
- HTTParty（API呼び出し）

**権限による制御**：
- Instagram認可を実行するにはAdministrator権限が必要
- 認可完了後のインボックス操作はエージェント権限でも可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 48 | インボックス新規作成画面 | 主画面 | 新しいInstagramチャネルの作成開始 |
| 49 | チャネル設定画面 | 主画面 | Instagram認可フローの開始 |
| 50 | インボックス設定画面 | 参照画面 | 認可完了後の設定画面 |

## 機能種別

OAuth認証 / 外部API連携 / データ作成・更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| code | String | Yes | Instagram認可コード | 有効な認可コード |
| state | String | Yes | CSRF対策用state（暗号化トークン） | 有効なトークン |

### 入力データソース

- Instagram OAuth認証コールバック
- 管理画面からの認可開始リクエスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| redirect_url | String | Instagram認可画面へのリダイレクトURL |
| inbox_id | Integer | 作成/更新されたインボックスID |
| instagram_channel | Object | 作成/更新されたInstagramチャネル情報 |

### 出力先

- データベース（channel_instagram, inboxes テーブル）
- 管理画面へのリダイレクト
- API レスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. 認可開始リクエスト（AuthorizationsController#create）
   └─ 管理者権限チェック（基底クラス）
   └─ stateトークン生成（generate_instagram_token）
   └─ Instagram OAuth認可URL生成

2. Instagramログイン・権限承認
   └─ ユーザーがInstagramでログイン
   └─ 必要な権限を承認
   └─ 認可コードを発行してコールバック

3. コールバック処理（CallbacksController#show）
   └─ エラーチェック（ユーザーキャンセル等）
   └─ stateトークン検証（verify_instagram_token）
   └─ 認可コードでトークン交換
   └─ 短期トークンから長期トークンへ変換

4. ユーザー情報取得
   └─ Instagram Graph APIでユーザー詳細取得
   └─ id, username, user_id, name, profile_picture_url, account_type

5. チャネル作成/更新
   └─ instagram_idで既存チャネル検索
   └─ 存在しない場合: 新規チャネル・インボックス作成
   └─ 存在する場合: トークン情報更新

6. 再認可処理
   └─ reauthorized!呼び出し

7. リダイレクト
   └─ 新規: エージェント追加画面へ
   └─ 更新: インボックス設定画面へ
```

### フローチャート

```mermaid
flowchart TD
    A[開始: Instagram連携開始] --> B[管理者権限チェック]
    B --> C{権限あり?}
    C -->|No| D[403エラー]
    C -->|Yes| E[state生成]
    E --> F[Instagram認可URL生成]
    F --> G[JSONレスポンス返却]
    G --> H[フロントエンドがリダイレクト]
    H --> I[Instagramログイン画面]
    I --> J{認証成功?}
    J -->|No| K[エラーリダイレクト]
    J -->|Yes| L[コールバック受信]
    L --> M{エラーパラメータ?}
    M -->|Yes| N[handle_authorization_error]
    M -->|No| O[state検証]
    O --> P{検証OK?}
    P -->|No| K
    P -->|Yes| Q[トークン交換]
    Q --> R[長期トークン変換]
    R --> S[ユーザー情報取得]
    S --> T{既存チャネルあり?}
    T -->|Yes| U[チャネル更新]
    T -->|No| V[新規チャネル作成]
    V --> W[インボックス作成]
    U --> X[reauthorized!]
    W --> X
    X --> Y[リダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-137-01 | 管理者権限必須 | Instagram認可はAdministrator権限が必要 | 認可開始時 |
| BR-137-02 | 長期トークン変換 | 短期トークンは長期トークンに変換して保存 | トークン取得後 |
| BR-137-03 | stateトークン検証 | verify_instagram_tokenでstateを検証 | コールバック時 |
| BR-137-04 | enable_fb_login無効 | enable_fb_login=0でFacebookログイン無効化 | 認可リクエスト時 |
| BR-137-05 | force_authentication | force_authentication=1で毎回認証要求 | 認可リクエスト時 |
| BR-137-06 | instagram_id一意 | instagram_idでチャネルを一意に識別 | チャネル検索時 |

### 計算ロジック

トークン有効期限の計算:
```ruby
expires_at = Time.current + @long_lived_token_response['expires_in'].seconds
```

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| チャネル作成 | channel_instagram | INSERT | 新規Instagramチャネル作成 |
| インボックス作成 | inboxes | INSERT | インボックス作成とチャネル関連付け |
| チャネル更新 | channel_instagram | UPDATE | トークン情報の更新 |
| インボックス更新 | inboxes | UPDATE | インボックス名の更新 |

### テーブル別操作詳細

#### channel_instagram

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | access_token | long_lived_token_response['access_token'] | 長期アクセストークン |
| INSERT/UPDATE | instagram_id | user_details['user_id'].to_s | InstagramユーザーID |
| INSERT/UPDATE | expires_at | Time.current + expires_in.seconds | トークン有効期限 |
| INSERT | account_id | account.id | アカウントID |

#### inboxes

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | name | user_details['username'] | Instagramユーザー名 |
| INSERT | channel_type | 'Channel::Instagram' | チャネルタイプ |
| INSERT | channel_id | 作成されたチャネルID | ポリモーフィック関連 |
| INSERT | account_id | account.id | アカウントID |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | Administrator権限なし | 管理者でログインし直す |
| 400 | OAuthException | 認可拒否・キャンセル | 再度認可フローを開始 |
| 500 | Internal Error | トークン交換失敗 | 再度認可フローを開始 |

### リトライ仕様

- トークン交換・変換失敗時、例外をキャプチャしてエラーページにリダイレクト

## トランザクション仕様

- 新規チャネル作成：`ActiveRecord::Base.transaction`内でチャネルとインボックスを作成

## パフォーマンス要件

- Instagram Graph API呼び出しは外部依存のため、タイムアウトを適切に設定
- 認可URLの生成は即座に完了

## セキュリティ考慮事項

- INSTAGRAM_APP_IDとINSTAGRAM_APP_SECRETは環境変数/GlobalConfigServiceで管理
- stateトークンによるCSRF対策
- アクセストークンは暗号化して保存

## 備考

- Instagram Business Login（OAuth 2.0）を使用
- 長期トークンの有効期限は約60日
- ユーザーキャンセル時は専用のエラーハンドリング

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: Instagram認証共通機能を理解する

Instagram OAuth設定の共通処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | instagram_concern.rb | `app/controllers/concerns/instagram_concern.rb` | instagram_clientの初期化、トークン処理 |

**主要処理フロー**:
- **4-16行目**: instagram_client - OAuth2::Clientの初期化
  - INSTAGRAM_APP_IDとINSTAGRAM_APP_SECRETをGlobalConfigServiceから取得
  - authorize_url: api.instagram.com/oauth/authorize
  - token_url: api.instagram.com/oauth/access_token
- **28-38行目**: exchange_for_long_lived_token - 長期トークンへの変換
- **40-48行目**: fetch_instagram_user_details - ユーザー情報取得

#### Step 2: 認可開始処理を理解する

認可URL生成のコントローラーを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | authorizations_controller.rb | `app/controllers/api/v1/accounts/instagram/authorizations_controller.rb` | 認可URL生成 |

**主要処理フロー**:
- **5-22行目**: create アクション
  - instagram_client.auth_code.authorize_url
  - redirect_uri: {base_url}/instagram/callback
  - scope: REQUIRED_SCOPESをカンマ区切り
  - enable_fb_login: '0'（Facebookログイン無効）
  - force_authentication: '1'（毎回認証要求）
  - state: generate_instagram_token(account_id)

#### Step 3: コールバック処理を理解する

認可コールバックの処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | callbacks_controller.rb | `app/controllers/instagram/callbacks_controller.rb` | Instagramコールバック処理 |

**主要処理フロー**:
- **6-16行目**: show アクション - エラーチェックと処理分岐
- **21-36行目**: process_successful_authorization - 成功時処理
- **67-76行目**: handle_authorization_error - ユーザーキャンセル時
- **90-106行目**: find_or_create_inbox - チャネル作成/更新
- **125-143行目**: create_channel_with_inbox - 新規作成

### プログラム呼び出し階層図

```
Api::V1::Accounts::Instagram::AuthorizationsController#create
    |
    +-- check_authorization (from OauthAuthorizationController)
    |
    +-- instagram_client.auth_code.authorize_url
    |       |
    |       +-- InstagramConcern#instagram_client
    |               |
    |               +-- OAuth2::Client.new(client_id, client_secret)
    |
    +-- generate_instagram_token(Current.account.id)
    |
    +-- render json: { success: true, url: redirect_url }

Instagram::CallbacksController#show
    |
    +-- error? チェック
    |       |
    |       +-- handle_authorization_error (エラー時)
    |
    +-- process_successful_authorization
            |
            +-- instagram_client.auth_code.get_token
            |
            +-- exchange_for_long_lived_token
            |       |
            |       +-- HTTParty.get (graph.instagram.com/access_token)
            |
            +-- find_or_create_inbox
                    |
                    +-- fetch_instagram_user_details
                    |       |
                    |       +-- HTTParty.get (graph.instagram.com/v22.0/me)
                    |
                    +-- find_channel_by_instagram_id
                    |
                    +-- update_channel or create_channel_with_inbox
                    |
                    +-- channel_instagram.reauthorized!
```

### データフロー図

```
[入力]                    [処理]                           [出力]

管理者リクエスト      --> AuthorizationsController#create --> 認可URL
(認可開始)               |                                  |
                         +-- instagram_client.authorize_url +-- { success, url }
                         +-- generate_instagram_token

Instagram OAuth      --> CallbacksController#show        --> チャネル・インボックス
(code, state)            |                                  |
                         +-- get_token(code)                +-- Channel::Instagram
                         +-- exchange_for_long_lived_token  +-- Inbox
                         +-- fetch_instagram_user_details   +-- 長期トークン
                         +-- find_or_create_inbox
                         +-- reauthorized!
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| instagram_concern.rb | `app/controllers/concerns/instagram_concern.rb` | Concern | Instagram OAuth設定 |
| authorizations_controller.rb | `app/controllers/api/v1/accounts/instagram/authorizations_controller.rb` | コントローラー | 認可URL生成 |
| callbacks_controller.rb | `app/controllers/instagram/callbacks_controller.rb` | コントローラー | Instagramコールバック |
| instagram.rb | `app/models/channel/instagram.rb` | モデル | Instagramチャネルのデータモデル |
| integration_helper.rb | `app/helpers/instagram/integration_helper.rb` | ヘルパー | stateトークン生成・検証 |
