# 画面設計書 90-MFA設定画面

## 概要

本ドキュメントは、Chatwootの多要素認証（MFA）設定画面の設計を記述したものです。この画面では、ユーザーの二要素認証の有効化、無効化、およびバックアップコードの管理を行います。

### 本画面の処理概要

**業務上の目的・背景**：セキュリティを強化するため、ユーザーがTOTP（Time-based One-Time Password）ベースの二要素認証を設定できるようにします。MFAを有効にすることで、パスワード漏洩時のリスクを軽減し、アカウントのセキュリティを向上させます。

**画面へのアクセス方法**：プロフィール設定画面からMFA設定カードをクリック、またはサイドメニューの「設定」から「プロフィール」を選択し、「MFA設定」をクリックしてアクセスします。URLパスは `/app/accounts/:accountId/profile/mfa` です。

**主要な操作・処理内容**：
1. MFAの有効化（QRコードスキャン → 認証コード入力 → バックアップコード取得）
2. MFAの無効化（パスワード + 認証コードで認証）
3. バックアップコードの再生成（認証コードで認証）
4. バックアップコードのダウンロード・コピー

**画面遷移**：
- 遷移元：プロフィール設定画面
- 遷移先：プロフィール設定画面（MFA無効時はリダイレクト）

**権限による表示制御**：全ロールでアクセス可能です。ただし、グローバル設定でMFAが無効な場合はプロフィール設定画面にリダイレクトされます。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | 多要素認証（MFA） | 主機能 | 多要素認証の設定 |
| 178 | MFA設定 | 補助機能 | MFA有効化・無効化 |
| 179 | バックアップコード | 補助機能 | MFAバックアップコードの生成 |

## 画面種別

設定

## URL/ルーティング

- **パス**: `/app/accounts/:accountId/profile/mfa`
- **ルート名**: `mfa_settings`

## 入出力項目

### MFA有効化

| 項目名 | データ型 | 必須 | バリデーション | 説明 |
|--------|----------|------|----------------|------|
| verificationCode | String | 必須 | 6桁の数字 | 認証アプリからのTOTPコード |

### MFA無効化

| 項目名 | データ型 | 必須 | バリデーション | 説明 |
|--------|----------|------|----------------|------|
| disablePassword | String | 必須 | - | 現在のパスワード |
| disableOtpCode | String | 必須 | 6桁の数字 | 認証コード |

### バックアップコード再生成

| 項目名 | データ型 | 必須 | バリデーション | 説明 |
|--------|----------|------|----------------|------|
| regenerateOtpCode | String | 必須 | 6桁の数字 | 認証コード |

## 表示項目

### MFA状態カード（MfaStatusCard）

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| 状態表示 | テキスト + アイコン | MFA有効/無効の状態 |
| 有効化ボタン | ボタン | MFA無効時に表示 |

### MFAセットアップウィザード（MfaSetupWizard）

#### Step 1: QRコード

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| 説明テキスト | テキスト | 設定手順の説明 |
| QRコード | 画像 | 認証アプリで読み取るQRコード |
| シークレットキー | 展開可能パネル | 手動入力用のシークレット |
| 認証コード入力 | テキストフィールド | 6桁のTOTPコード |
| 検証ボタン | ボタン | コード検証を実行 |
| キャンセルボタン | ボタン | セットアップを中止 |

#### Step 2: バックアップコード

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| 警告メッセージ | アラート | バックアップコードの重要性 |
| コード一覧 | グリッド | バックアップコード（10個程度） |
| ダウンロードボタン | ボタン | テキストファイルとしてダウンロード |
| コピーボタン | ボタン | クリップボードにコピー |
| 確認チェックボックス | チェックボックス | コード保存の確認 |
| 完了ボタン | ボタン | セットアップ完了 |

### MFA管理アクション（MfaManagementActions）

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| バックアップコード再生成カード | カード | バックアップコード再生成機能 |
| MFA無効化カード | カード | MFA無効化機能 |

## イベント仕様

### 1-画面マウント時

onMountedで以下を実行：
1. isMfaEnabledがfalseの場合、プロフィール設定画面にリダイレクト
2. `mfaAPI.get()`でMFA状態を取得

### 2-MFA有効化開始

「有効化」ボタン押下時：
1. `mfaAPI.enable()`を実行
2. provisioningUri, secretKey, qrCodeUrlを取得
3. セットアップウィザードを表示

### 3-認証コード検証

検証ボタン押下時：
1. `mfaAPI.verify(verificationCode)`を実行
2. 成功時：バックアップコードを取得、Step 2に進む
3. 失敗時：エラーメッセージを表示

### 4-MFAセットアップ完了

完了ボタン押下時：
1. mfaEnabled = true, backupCodesGenerated = trueに設定
2. 成功メッセージを表示
3. ウィザードを閉じる

### 5-MFA無効化

無効化ダイアログの確認ボタン押下時：
1. `mfaAPI.disable(password, otpCode)`を実行
2. 成功時：mfaEnabled = falseに設定、成功メッセージを表示
3. 失敗時：エラーメッセージを表示

### 6-バックアップコード再生成

再生成ダイアログの確認ボタン押下時：
1. `mfaAPI.regenerateBackupCodes(otpCode)`を実行
2. 成功時：新しいバックアップコードを表示、成功メッセージ
3. 失敗時：エラーメッセージを表示

### 7-バックアップコードダウンロード

ダウンロードボタン押下時：
1. テキストファイル（chatwoot-backup-codes.txt）としてダウンロード

### 8-バックアップコードコピー

コピーボタン押下時：
1. クリップボードにコピー
2. 成功メッセージを表示

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| MFA状態取得 | users | SELECT | mfa_enabled状態の取得 |
| MFA有効化開始 | users | UPDATE | otp_secret生成・保存 |
| 認証コード検証 | users | UPDATE | mfa_enabled = true |
| MFA無効化 | users | UPDATE | mfa_enabled = false, otp_secret削除 |
| バックアップコード再生成 | users | UPDATE | backup_codes再生成 |

### テーブル別更新項目詳細

#### users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | mfa_enabled, backup_codes_generated | id = current_user.id | 状態取得 |
| UPDATE | otp_secret | 新規生成されたシークレット | MFA有効化開始時 |
| UPDATE | mfa_enabled | true | 検証成功時 |
| UPDATE | mfa_enabled | false | MFA無効化時 |
| UPDATE | otp_secret | null | MFA無効化時 |
| UPDATE | backup_codes | 新規生成されたコード | 検証成功/再生成時 |

## メッセージ仕様

| メッセージID | 種別 | 表示条件 | メッセージ内容 |
|-------------|------|----------|--------------|
| MFA_SETTINGS.SETUP.SUCCESS | 成功 | MFA有効化成功時 | MFAが正常に有効化されました |
| MFA_SETTINGS.SETUP.ERROR_STARTING | エラー | MFA有効化開始失敗時 | MFAの有効化を開始できませんでした |
| MFA_SETTINGS.SETUP.INVALID_CODE | エラー | 検証コード無効時 | 無効な認証コードです |
| MFA_SETTINGS.SETUP.SECRET_COPIED | 成功 | シークレットコピー時 | シークレットキーがコピーされました |
| MFA_SETTINGS.BACKUP.CODES_COPIED | 成功 | バックアップコードコピー時 | バックアップコードがコピーされました |
| MFA_SETTINGS.DISABLE.SUCCESS | 成功 | MFA無効化成功時 | MFAが正常に無効化されました |
| MFA_SETTINGS.DISABLE.ERROR | エラー | MFA無効化失敗時 | MFAの無効化に失敗しました |
| MFA_SETTINGS.REGENERATE.SUCCESS | 成功 | 再生成成功時 | バックアップコードが再生成されました |
| MFA_SETTINGS.REGENERATE.ERROR | エラー | 再生成失敗時 | バックアップコードの再生成に失敗しました |

## 例外処理

| 例外条件 | 処理内容 |
|----------|----------|
| MFA機能無効 | プロフィール設定画面にリダイレクト |
| 認証コード無効 | エラーメッセージを表示、入力フィールドをクリア |
| パスワード無効 | エラーメッセージを表示 |
| API呼び出し失敗 | エラーメッセージを表示 |

## 備考

- MFA機能はグローバル設定（chatwootConfig.isMfaEnabled）で有効化されている場合のみ利用可能
- バックアップコードは認証アプリが使えない場合の緊急用
- 各バックアップコードは1回のみ使用可能
- QRコードはqrcodeライブラリで生成（クライアントサイド）
- バックアップコードは検証成功時に返却される（有効化開始時には返却されない）

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | mfa.js | `app/javascript/dashboard/api/mfa.js` | APIクライアントのエンドポイント（get, enable, verify, disable, regenerateBackupCodes） |
| 1-2 | mfa_controller.rb | `app/controllers/api/v1/profile/mfa_controller.rb` | バックエンドのアクション、バリデーション |

**読解のコツ**: MFAのフローは enable → verify → (disable/regenerateBackupCodes) の順序

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | MfaSettings.vue | `app/javascript/dashboard/routes/dashboard/settings/profile/MfaSettings.vue` | 状態管理（mfaEnabled, showSetup, backupCodes等）、各ハンドラ関数 |

**主要処理フロー**:
1. **行17-23**: refで状態を管理（mfaEnabled, backupCodesGenerated, showSetup, provisioningUri, qrCodeUrl, secretKey, backupCodes）
2. **行30-50**: onMountedでMFA機能チェックとAPI呼び出し
3. **行53-73**: startMfaSetupでenable APIを呼び出し
4. **行76-90**: verifyCodeでverify APIを呼び出し
5. **行93-98**: completeMfaSetupでセットアップ完了処理
6. **行106-116**: disableMfaでdisable APIを呼び出し
7. **行119-129**: regenerateBackupCodesでbackup_codes APIを呼び出し

#### Step 3: コンポーネント層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | MfaStatusCard.vue | `app/javascript/dashboard/routes/dashboard/settings/profile/MfaStatusCard.vue` | MFA状態表示、有効化ボタン |
| 3-2 | MfaSetupWizard.vue | `app/javascript/dashboard/routes/dashboard/settings/profile/MfaSetupWizard.vue` | QRコード表示、コード入力、バックアップコード表示 |
| 3-3 | MfaManagementActions.vue | `app/javascript/dashboard/routes/dashboard/settings/profile/MfaManagementActions.vue` | 無効化、バックアップコード再生成のダイアログ |

**主要処理フロー（MfaSetupWizard.vue）**:
- **行43**: setupStep refで'qr'/'backup'を管理
- **行50-64**: generateQRCodeでqrcodeライブラリを使用
- **行67-77**: watchでprovisioningUriからQRコード生成
- **行79-88**: verifyCodeでemit('verify')を発火
- **行101-110**: downloadBackupCodesでテキストファイル生成

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

```
MfaSettings.vue (エントリーポイント)
    │
    ├─ onMounted
    │      ├─ isMfaEnabled チェック（false → redirect）
    │      └─ mfaAPI.get() → mfaEnabled, backupCodesGenerated取得
    │
    ├─ MfaStatusCard.vue
    │      └─ @enable-mfa → startMfaSetup
    │              └─ mfaAPI.enable()
    │                      └─ provisioningUri, secretKey取得
    │
    ├─ MfaSetupWizard.vue（v-if="showSetup && !mfaEnabled"）
    │      │
    │      ├─ Step 1: QRコード
    │      │      ├─ QRCode.toDataURL() でQRコード生成
    │      │      ├─ @verify → verifyCode
    │      │      │      └─ mfaAPI.verify() → backupCodes取得
    │      │      └─ @cancel → cancelSetup
    │      │
    │      └─ Step 2: バックアップコード
    │              ├─ downloadBackupCodes → Blobダウンロード
    │              ├─ copyBackupCodes → クリップボードコピー
    │              └─ @complete → completeMfaSetup
    │
    └─ MfaManagementActions.vue（v-if="mfaEnabled"）
           │
           ├─ @disable-mfa → disableMfa
           │      └─ mfaAPI.disable(password, otpCode)
           │
           └─ @regenerate-backup-codes → regenerateBackupCodes
                  └─ mfaAPI.regenerateBackupCodes(otpCode)
                          └─ 新しいbackupCodes取得
```

### データフロー図

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

onMounted ──────────────▶ mfaAPI.get() ────────────────▶ mfaEnabled状態
                              │
有効化ボタン押下 ────────────▶ mfaAPI.enable() ───────────▶ provisioningUri, secretKey
                              │
                              ▼
                      QRCode.toDataURL()
                              │
                              ▼
                      QRコード表示
                              │
認証コード入力 ────────────────▶
                              │
                              ▼
                      mfaAPI.verify() ─────────────────▶ backupCodes
                              │                                │
                              ▼                                ▼
                      mfaEnabled = true               DB更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| MfaSettings.vue | `app/javascript/dashboard/routes/dashboard/settings/profile/MfaSettings.vue` | ソース | メインページコンポーネント |
| MfaStatusCard.vue | `app/javascript/dashboard/routes/dashboard/settings/profile/MfaStatusCard.vue` | ソース | MFA状態表示カード |
| MfaSetupWizard.vue | `app/javascript/dashboard/routes/dashboard/settings/profile/MfaSetupWizard.vue` | ソース | セットアップウィザード |
| MfaManagementActions.vue | `app/javascript/dashboard/routes/dashboard/settings/profile/MfaManagementActions.vue` | ソース | MFA管理アクション |
| mfa.js | `app/javascript/dashboard/api/mfa.js` | ソース | APIクライアント |
| mfa_controller.rb | `app/controllers/api/v1/profile/mfa_controller.rb` | ソース | バックエンドコントローラー |
| management_service.rb | `app/services/mfa/management_service.rb` | ソース | MFA管理サービス |
| authentication_service.rb | `app/services/mfa/authentication_service.rb` | ソース | MFA認証サービス |
| Input.vue | `app/javascript/dashboard/components-next/input/Input.vue` | ソース | 入力コンポーネント |
| Button.vue | `app/javascript/dashboard/components-next/button/Button.vue` | ソース | ボタンコンポーネント |
| Dialog.vue | `app/javascript/dashboard/components-next/dialog/Dialog.vue` | ソース | ダイアログコンポーネント |
