# 画面設計書 4-マジックリンク送信完了画面

## 概要

本ドキュメントは、Fizzyアプリケーションのマジックリンク送信完了画面（`app/views/sessions/magic_links/show.html.erb`）の設計仕様を記述したものです。

### 本画面の処理概要

マジックリンク送信完了画面は、ログインまたはサインアップ時にメールアドレスを入力した後、認証コード（マジックリンクコード）の入力を促す画面です。ユーザーはメールで受信した6桁の認証コードを入力することで、サインインまたはサインアップ処理が完了します。

**業務上の目的・背景**：パスワードレス認証（マジックリンク認証）の中核となる画面です。ユーザーがメールアドレス所有者であることを確認するため、メールに送信された一時的なコードの入力を要求します。これにより、パスワードを使用せずにセキュアな認証を実現しています。コードの有効期限は15分間であり、不正アクセスのリスクを最小限に抑えています。

**画面へのアクセス方法**：ログイン画面（`/session/new`）またはサインアップ画面（`/signup/new`）でメールアドレスを送信した後、自動的にリダイレクトされます。`pending_authentication_token`クッキーが設定されていない場合は、ログイン画面にリダイレクトされます。

**主要な操作・処理内容**：
1. メールで受信した6桁の認証コードを入力する
2. Enterキー押下またはペースト時に自動的にフォームが送信される
3. コードが有効な場合、認証が完了しセッションが開始される
4. サインアップ目的の場合はサインアップ完了画面に、それ以外は元のページまたはランディングページにリダイレクトされる
5. コードが無効な場合は画面が振動し、再入力を促す

**画面遷移**：
- 遷移元：ログイン画面（`/session/new`）、サインアップ画面（`/signup/new`）
- 遷移先：サインアップ完了画面（サインアップ目的の場合）、ランディングページまたは認証前にアクセスしようとしたページ、ログイン画面（エラー時）

**権限による表示制御**：
- 未認証ユーザーのみアクセス可能（認証済みの場合はルートにリダイレクト）
- `pending_authentication_token`クッキーが必須（なければログイン画面にリダイレクト）
- 開発環境ではマジックリンクコードが画面に表示される

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | サインイン（マジックリンク） | 主機能 | マジックリンク送信後の確認画面表示とコード検証 |

## 画面種別

認証（コード入力）

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| URL | `/session/magic_link` |
| HTTPメソッド | GET（表示）、POST（コード検証） |
| コントローラー | `Sessions::MagicLinksController` |
| アクション | `show`（表示）、`create`（コード検証） |
| レイアウト | `public` |

## 入出力項目

### 入力項目

| No | 項目名 | 物理名 | データ型 | 必須 | 最大長 | 入力形式 | バリデーション | 備考 |
|----|--------|--------|---------|------|--------|----------|---------------|------|
| 1 | 認証コード | code | string | 必須 | 6 | text | - | autocomplete="one-time-code", 大文字変換表示 |

### 出力項目

| No | 項目名 | データ型 | 説明 |
|----|--------|---------|------|
| 1 | session_token | string | セッショントークン（JSON応答時） |
| 2 | requires_signup_completion | boolean | サインアップ完了が必要かどうか（JSON応答時） |

## 表示項目

| No | 項目名 | 表示条件 | データソース | 備考 |
|----|--------|----------|-------------|------|
| 1 | タイトル「Check your email」 | 常時 | 固定文言 | - |
| 2 | 案内文 | 常時 | 固定文言 | "Then enter the verification code..." |
| 3 | コード入力フィールド | 常時 | - | placeholder="------", maxlength=6 |
| 4 | メールアドレス表示 | 常時 | email_address_pending_authentication | 送信先メールアドレス |
| 5 | 有効期限表示 | 常時 | MagicLink::EXPIRATION_TIME | "15 minutes" |
| 6 | 開発用コード表示 | Rails.env.development? && flash[:magic_link_code] | flash[:magic_link_code] | DEVバッジ付き |
| 7 | フッター | 常時 | _footer部分テンプレート | サポートメール連絡先 |

## イベント仕様

### 1-認証コード入力・送信

**トリガー**: フォーム送信（Enterキー押下、ペースト時に自動送信）

**処理フロー**:
1. `Sessions::MagicLinksController#create`が呼び出される
2. レート制限チェック（15分間に10回まで）
3. `MagicLink.consume(code)`で該当するマジックリンクを検索・消費
4. マジックリンクが存在する場合：
   - メールアドレスの一致確認（secure_compare使用）
   - 一致する場合：セッション開始、リダイレクト
   - 一致しない場合：エラー、ログイン画面にリダイレクト
5. マジックリンクが存在しない場合（期限切れ含む）：
   - 画面を振動させて再表示

**レスポンス**:
- HTML成功時（サインイン）: 認証前のページまたはランディングページにリダイレクト
- HTML成功時（サインアップ）: サインアップ完了画面（`/signup/completion/new`）にリダイレクト
- HTML失敗時（無効なコード）: 同画面を振動表示で再表示
- HTML失敗時（メールアドレス不一致）: ログイン画面にリダイレクト（alertメッセージ付き）
- JSON成功時: `{ session_token: "...", requires_signup_completion: true/false }`
- JSON失敗時: `{ message: "Try another code." }`（status: 401）

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| コード検証成功 | magic_links | DELETE | マジックリンクレコード削除（consume） |
| コード検証成功 | sessions | INSERT | 新規セッションレコード作成 |

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

#### magic_links

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | code | 入力されたコード（正規化済み） | active scopeで検索後に削除 |

#### sessions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | identity_id | マジックリンクに紐づくIdentityのID | 外部キー |
| INSERT | user_agent | request.user_agent | ブラウザ情報 |
| INSERT | ip_address | request.remote_ip | IPアドレス |
| INSERT | created_at | 現在日時 | 自動設定 |
| INSERT | updated_at | 現在日時 | 自動設定 |

## メッセージ仕様

| No | メッセージ種別 | メッセージ内容 | 表示条件 |
|----|--------------|---------------|---------|
| 1 | エラー | Enter your email address to sign in. | pending_authentication_tokenがない場合 |
| 2 | エラー | Something went wrong. Please try again. | メールアドレス不一致時 |
| 3 | エラー | Try another code. | 無効なコード入力時（JSON） |
| 4 | エラー | Try again in 15 minutes. | レート制限超過時 |
| 5 | 視覚フィードバック | shakeアニメーション | 無効なコード入力時 |

## 例外処理

| 例外パターン | 対応内容 | 備考 |
|------------|---------|------|
| pending_authentication_tokenなし | alertメッセージでログイン画面にリダイレクト | before_actionでチェック |
| 無効なコード | 画面振動（shake）で同画面を再表示 | flash[:shake] = true |
| 期限切れコード | 画面振動（shake）で同画面を再表示 | MagicLink.active scopeで検索 |
| メールアドレス不一致 | alertメッセージでログイン画面にリダイレクト | セキュリティ対策 |
| レート制限超過 | alertメッセージで同画面にリダイレクト | 15分間に10回まで |

## 備考

- コード入力フィールドには`data-1p-ignore`が設定されており、1Passwordの自動入力を無効化
- `autocomplete="one-time-code"`により、SMSやメールからの自動入力をサポート
- Stimulus.jsの`magic-link`コントローラーにより、Enterキーやペースト時の自動送信が実装
- コードは大文字で表示される（`txt-uppercase`クラス）
- メールアドレスの比較には`ActiveSupport::SecurityUtils.secure_compare`を使用（タイミング攻撃対策）
- `MagicLink.consume`はマジックリンクを取得と同時に削除（ワンタイム使用）
- 開発環境では、flash[:magic_link_code]にコードが設定されている場合、画面上に表示される

---

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

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

### 推奨読解順序

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

まず、マジックリンク検証に関わるデータモデルの構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | magic_link.rb | `app/models/magic_link.rb` | consumeメソッド、active scope、コード検証ロジック |
| 1-2 | identity.rb | `app/models/identity.rb` | Identityとsessionsの関連 |

**読解のコツ**: `MagicLink.consume(code)`は、有効期限内のマジックリンクを検索し、見つかれば削除して返すワンタイム処理です。`active` scopeが期限切れチェックを行います。

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

処理の起点となるコントローラーを特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | magic_links_controller.rb | `app/controllers/sessions/magic_links_controller.rb` | show/createアクション、認証フロー |

**主要処理フロー**:
1. **5行目**: `ensure_that_email_address_pending_authentication_exists` - 前提条件チェック
2. **9-10行目**: `show`アクション - 画面表示のみ
3. **12-18行目**: `create`アクション - コード検証と認証
4. **35-41行目**: `authenticate` - メールアドレス一致確認とサインイン
5. **43-51行目**: `sign_in` - セッション開始とリダイレクト
6. **70-76行目**: `after_sign_in_url` - リダイレクト先の決定

#### Step 3: 認証モジュールを理解する

セッション管理とクッキー処理を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | authentication.rb | `app/controllers/concerns/authentication.rb` | start_new_session_for、セッションCookie設定 |
| 3-2 | via_magic_link.rb | `app/controllers/concerns/authentication/via_magic_link.rb` | pending_authentication_token関連メソッド |

**主要処理フロー**:
- **52-55行目（via_magic_link.rb）**: `email_address_pending_authentication` - クッキーからメールアドレス取得
- **64-66行目（via_magic_link.rb）**: `clear_pending_authentication_token` - クッキー削除
- **88-92行目（authentication.rb）**: `start_new_session_for` - 新規セッション作成

#### Step 4: ビューとテンプレートを理解する

画面表示のテンプレートを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | show.html.erb | `app/views/sessions/magic_links/show.html.erb` | フォーム構造、Stimulus.js連携、開発環境表示 |

**主要処理フロー**:
- **3行目**: shakeクラスの条件付き付与
- **11-16行目**: コード入力フォーム（Stimulus.jsコントローラー）
- **18行目**: メールアドレスと有効期限表示
- **21-27行目**: 開発環境用コード表示

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

```
Sessions::MagicLinksController#create
    │
    ├─ [before_action] ensure_that_email_address_pending_authentication_exists
    │      └─ email_address_pending_authentication
    │             └─ [nil] redirect_to new_session_path
    │
    └─ MagicLink.consume(code)
           │
           ├─ [nil] invalid_code
           │      └─ redirect_to session_magic_link_path (flash[:shake])
           │
           └─ [magic_link] authenticate(magic_link)
                  │
                  ├─ secure_compare(email_address, magic_link.identity.email_address)
                  │      │
                  │      └─ [false] email_address_mismatch
                  │             └─ redirect_to new_session_path
                  │
                  └─ [true] sign_in(magic_link)
                         │
                         ├─ clear_pending_authentication_token
                         │
                         ├─ start_new_session_for(identity)
                         │      └─ Session.create!
                         │
                         └─ redirect_to after_sign_in_url
                                │
                                ├─ [for_sign_up?] new_signup_completion_path
                                │
                                └─ [otherwise] after_authentication_url
```

### データフロー図

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

code ───────────────▶ MagicLinksController#create
                              │
                              ├──▶ MagicLink.consume(code)
                              │        │
                              │        ├─[nil]─▶ 画面振動で再表示
                              │        │
                              │        └─[found]─▶ authenticate
                              │                         │
                              │                         ├─▶ secure_compare
                              │                         │        │
                              │                         │        └─[NG]─▶ ログイン画面へ
                              │                         │
                              │                         └─[OK]─▶ sign_in
                              │                                       │
                              │                                       ├─▶ Session作成
                              │                                       │
                              │                                       └─▶ Cookie設定
                              │
                              └──▶ リダイレクト
                                       │
                                       ├─[sign_up]─▶ /signup/completion/new
                                       │
                                       └─[sign_in]─▶ landing or return_to
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| magic_links_controller.rb | `app/controllers/sessions/magic_links_controller.rb` | コントローラー | マジックリンク検証の制御 |
| show.html.erb | `app/views/sessions/magic_links/show.html.erb` | ビュー | コード入力画面テンプレート |
| _footer.html.erb | `app/views/sessions/_footer.html.erb` | パーシャル | フッター表示 |
| magic_link.rb | `app/models/magic_link.rb` | モデル | マジックリンクの管理・検証 |
| identity.rb | `app/models/identity.rb` | モデル | ユーザー識別情報の管理 |
| authentication.rb | `app/controllers/concerns/authentication.rb` | Concern | 認証共通処理 |
| via_magic_link.rb | `app/controllers/concerns/authentication/via_magic_link.rb` | Concern | マジックリンク認証処理 |
| magic_link_controller.js | `app/javascript/controllers/magic_link_controller.js` | Stimulus | 自動送信制御 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
