# 画面設計書 2-サインイン確認画面

## 概要

本ドキュメントは、Ghost管理画面のサインイン確認画面（2FA検証画面）の設計内容を記載する。

### 本画面の処理概要

サインイン確認画面は、サインイン時に2段階認証（2FA）または新規デバイス検証が必要な場合に表示される画面である。メールで送信された6桁の検証コードを入力し、セッションを検証済み状態にする機能を提供する。

**業務上の目的・背景**：セキュリティ強化のため、新規デバイスからのログインや2FA必須設定が有効な場合に、メールベースの本人確認を要求する。これにより、パスワードが漏洩した場合でも、メールアカウントへのアクセスがなければ不正ログインを防止できる。

**画面へのアクセス方法**：サインイン画面でメールアドレスとパスワードによる認証が成功した後、以下の条件で本画面に自動遷移する：
1. サイト設定で2FA（メールMFA）が必須に設定されている場合
2. 新規デバイス（未認識のブラウザ/デバイス）からのログインの場合

**主要な操作・処理内容**：
1. 検証コード入力 - メールで受信した6桁の数字コードを入力
2. 検証ボタン押下 - 入力されたコードを検証し、成功すればホーム画面へ遷移
3. 再送信リンク押下 - 新しい検証コードをメールで再送信

**画面遷移**：
- 遷移元：サインイン画面（認証成功かつ検証必要時）
- 遷移先：
  - 検証成功時：ホーム画面
  - 検証失敗時：本画面に留まりエラー表示

**権限による表示制御**：
- 2FA必須設定時と新規デバイス検出時でヘッダーテキストが異なる
- 2FA必須時：「2FA confirmation」
- 新規デバイス時：「Verify it's really you」

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 88 | 認証 | 主機能 | 2段階認証コードの検証処理 |

## 画面種別

認証（2段階認証）

## URL/ルーティング

- **URL**: `/ghost/signin-verify`
- **Emberルート名**: `signin-verify`
- **ルートファイル**: `ghost/admin/app/routes/signin-verify.js`

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 入力/出力 | バリデーション | 備考 |
|--------|--------|-----|------|----------|---------------|------|
| 検証コード | token | string | 必須 | 入力 | 6桁の数字のみ | inputmode="numeric"、pattern="[0-9]*" |

## 表示項目

| 項目名 | 表示条件 | データソース | 備考 |
|--------|----------|--------------|------|
| サイトアイコン | 常時 | config.siteIcon | スタイル属性で表示 |
| ヘッダータイトル | 常時 | twoFactorRequired条件分岐 | "2FA confirmation" または "Verify it's really you" |
| 説明文 | 常時 | twoFactorRequired条件分岐 | 2FA必須/新規デバイスで異なるテキスト |
| 再送信ボタン | 常時 | - | クールダウン中は「Sent」表示 |
| フローエラー/通知 | エラー発生時 | flowErrors, verifyData.validationMessage | 画面下部に赤字表示 |

## イベント仕様

### 1-検証ボタン押下（Verify）

**トリガー**: 「Verify →」ボタンクリックまたはEnterキー押下

**処理フロー**:
1. `verifyTokenTask`が実行される
2. クライアント側バリデーション実行（必須チェック、6桁数字チェック）
3. バリデーション成功時、`session.authenticate('authenticator:cookie', {token})`を実行
4. APIエンドポイント`PUT /ghost/api/admin/session/verify`に検証リクエスト送信
5. バックエンドでOTPトークンを検証
6. 検証成功時：セッションをverified状態に更新、ホーム画面へリダイレクト
7. 検証失敗時：エラーメッセージを表示

**遷移先**:
- 成功時: `/ghost/` (ホーム画面)
- 失敗時: 本画面に留まる

### 2-再送信ボタン押下（Resend）

**トリガー**: 「Resend」ボタンクリック

**処理フロー**:
1. `resendTokenTask`が実行される
2. APIエンドポイント`POST /ghost/api/admin/session/verify`にリクエスト送信
3. バックエンドで新しいOTPコードを生成し、メール送信
4. 成功時：15秒間のクールダウン開始、「Sent」表示
5. 失敗時：エラーメッセージを表示

### 3-検証コード入力

**トリガー**: 検証コード入力欄への入力

**処理フロー**:
1. `handleTokenInput`アクションが実行される
2. エラー状態をリセット
3. 入力値を`verifyData.token`に設定

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 検証成功 | sessions | UPDATE | verified フラグを true に更新 |

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

#### sessions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | verified | true | OTP検証成功時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| M001 | エラー | "Verification code is required" | 検証コード未入力 |
| M002 | エラー | "Verification code must be 6 numbers" | 6桁数字以外の入力 |
| M003 | エラー | "Your verification code is incorrect." | OTP検証失敗（401） |
| M004 | エラー | "There was a problem verifying the code. Please try again." | その他のエラー |
| M005 | エラー | "There was a problem resending the verification token." | 再送信失敗 |
| M006 | 表示 | "Sent" | 再送信成功、クールダウン中 |
| M007 | 表示 | "Enter the sign-in verification code sent to your email." | 2FA必須時の説明文 |
| M008 | 表示 | "It looks like you're signing in from a new device..." | 新規デバイス時の説明文 |

## 例外処理

| 例外ケース | 対応処理 | 表示内容 |
|-----------|----------|----------|
| 検証コード未入力 | バリデーションエラー表示 | "Verification code is required" |
| 検証コード形式不正 | バリデーションエラー表示 | "Verification code must be 6 numbers" |
| OTP検証失敗 | エラー表示、トークン欄にエラーマーク | "Your verification code is incorrect." |
| セッション無効 | エラー表示 | APIエラーメッセージ |
| 再送信失敗 | エラー表示 | "There was a problem resending the verification token." |
| 通信エラー | エラー表示 | "There was a problem verifying the code. Please try again." |

## 備考

- OTP（ワンタイムパスワード）はTOTPアルゴリズムベースで生成される
- 検証コードの有効期限は設定により異なる（admin_session_secretを使用）
- 再送信ボタンにはスパム防止のため15秒のクールダウンがある
- 新規デバイス検出はユーザーエージェントとIPアドレスを基に判定
- スタッフデバイス検証は`security:staffDeviceVerification`設定で無効化可能

---

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

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

### 推奨読解順序

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

検証データの構造とバリデーションルールを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | signin-verify.js (Controller) | `ghost/admin/app/controllers/signin-verify.js` | VerifyDataクラス（19-57行目）のデータ構造とバリデーション |

**読解のコツ**: VerifyDataクラスは内部クラスとして定義され、トークンの保持とバリデーションを担当。`@tracked`デコレータでリアクティブなプロパティを定義している。

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

テンプレートとコントローラーの関係を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | signin-verify.hbs | `ghost/admin/app/templates/signin-verify.hbs` | フォーム構造、条件分岐（2FA/新規デバイス）、イベントバインディング |
| 2-2 | signin-verify.js (Route) | `ghost/admin/app/routes/signin-verify.js` | setupControllerでのデータ初期化 |

**主要処理フロー**:
1. **4行目**: フォーム送信時に`verifyTokenTask`が実行される
2. **7行目**: twoFactorRequiredによる条件分岐でヘッダー表示を切り替え
3. **33行目**: 入力イベントで`handleTokenInput`アクションが呼ばれる
4. **36-51行目**: 再送信ボタンの状態制御（クールダウン表示）

#### Step 3: コントローラーのタスク処理を理解する

ember-concurrencyのtaskを使った非同期処理の流れを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | signin-verify.js (Controller) | `ghost/admin/app/controllers/signin-verify.js` | verifyTokenTask（96-117行目）、resendTokenTask（119-139行目）、delayResendAvailabilityTask（141-148行目）の処理フロー |

**主要処理フロー**:
- **69-78行目**: コンストラクタでsession.errorCodeを読み取り、2FA必須フラグを設定
- **96-117行目**: 検証タスク - バリデーション後、authenticateを実行
- **105行目**: `session.authenticate('authenticator:cookie', {token})`でトークンのみ認証
- **119-139行目**: 再送信タスク - POST /session/verifyでコード再送信

#### Step 4: 認証処理（Authenticator）を理解する

トークンのみでの認証フローを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cookie.js | `ghost/admin/app/authenticators/cookie.js` | authenticate関数（22-48行目）でトークンのみ認証のパス |

**主要処理フロー**:
- **23-32行目**: tokenのみでidentification/passwordがない場合、PUT /session/verifyを呼び出し

#### Step 5: バックエンドAPI処理を理解する

サーバーサイドの検証処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | session.js | `ghost/core/core/server/api/endpoints/session.js` | verify関数（74-78行目）でミドルウェア呼び出し |
| 5-2 | middleware.js | `ghost/core/core/server/services/auth/session/middleware.js` | verifyAuthCode関数（71-84行目）で検証処理 |
| 5-3 | session-service.js | `ghost/core/core/server/services/auth/session/session-service.js` | verifyAuthCodeForUser（171-175行目）、sendAuthCodeToUser（245-289行目）の実装詳細 |

**主要処理フロー**:
- **middleware.js 71-84行目**: OTP検証成功時にsession.verifiedをtrueに設定
- **session-service.js 171-175行目**: OTPライブラリを使用したトークン検証
- **session-service.js 245-289行目**: メール送信処理（デバイス情報、位置情報含む）

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

```
signin-verify.hbs (テンプレート)
    │
    ├─ SigninVerifyController.verifyTokenTask
    │      ├─ VerifyData.validate (クライアントバリデーション)
    │      └─ session.authenticate('authenticator:cookie', {token})
    │             └─ CookieAuthenticator.authenticate
    │                    └─ PUT /ghost/api/admin/session/verify
    │                           └─ SessionController.verify
    │                                  └─ SessionMiddleware.verifyAuthCode
    │                                         ├─ sessionService.verifyAuthCodeForUser
    │                                         │      └─ otp.verify
    │                                         └─ sessionService.verifySession
    │
    └─ SigninVerifyController.resendTokenTask
           └─ POST /ghost/api/admin/session/verify
                  └─ SessionController.sendVerification
                         └─ SessionMiddleware.sendAuthCode
                                └─ sessionService.sendAuthCodeToUser
                                       ├─ generateAuthCodeForUser
                                       │      └─ otp.generate
                                       ├─ getDeviceDetails
                                       │      └─ getGeolocationFromIP
                                       └─ mailer.send
```

### データフロー図

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

token ────────────────────▶ verifyTokenTask ──────────────────▶ ホーム画面へ遷移
(6桁数字)                         │                              または
                                 │                              エラー表示
                                 ▼
                           PUT /session/verify
                                 │
                                 ▼
                           otp.verify
                                 │
                           ┌─────┴─────┐
                           │           │
                        成功         失敗
                           │           │
                           ▼           ▼
                    session.verified   401 Unauthorized
                       = true
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| signin-verify.hbs | `ghost/admin/app/templates/signin-verify.hbs` | テンプレート | 検証フォームのUI定義 |
| signin-verify.js | `ghost/admin/app/routes/signin-verify.js` | ルート | ルーティングとセットアップ |
| signin-verify.js | `ghost/admin/app/controllers/signin-verify.js` | コントローラー | フォーム処理とタスク定義 |
| cookie.js | `ghost/admin/app/authenticators/cookie.js` | 認証 | トークン検証API呼び出し |
| session.js | `ghost/core/core/server/api/endpoints/session.js` | API | セッション管理エンドポイント |
| middleware.js | `ghost/core/core/server/services/auth/session/middleware.js` | ミドルウェア | セッション操作ミドルウェア |
| session-service.js | `ghost/core/core/server/services/auth/session/session-service.js` | サービス | セッションサービス実装 |
| otp.js | `ghost/core/core/server/services/auth/otp.js` | ユーティリティ | OTPトークン生成・検証 |
| signin.js | `ghost/core/core/server/services/auth/session/emails/signin.js` | テンプレート | 検証コードメールテンプレート |
| unauthenticated.js | `ghost/admin/app/routes/unauthenticated.js` | ルート（親） | 未認証ルートの基底クラス |
