# 機能設計書 72-メールアドレス確認

## 概要

本ドキュメントは、Fizzyアプリケーションにおけるメールアドレス変更の確認（トークン検証）機能の設計を記述する。

### 本機能の処理概要

メールアドレス変更リクエスト（No.71）で送信された確認メールのリンクをクリックした際に、トークンを検証してメールアドレスの変更を確定する機能である。トークンが有効な場合、ユーザーのIdentityを新しいメールアドレスに紐づけ、セッションを更新する。

**業務上の目的・背景**：メールアドレス変更のセキュリティを確保するため、新しいメールアドレスの所有者であることを確認する必要がある。この機能は、確認メールに含まれるトークンを検証し、正当な所有者のみがメールアドレス変更を完了できるようにする。

**機能の利用シーン**：
- ユーザーがメールアドレス変更確認メールを受信し、リンクをクリックした場合
- 確認リンクの有効期限（30分）内にアクセスした場合
- 確認リンクの有効期限が切れている場合のエラー表示

**主要な処理内容**：
1. 確認リンクからユーザーIDとトークンを取得
2. トークンを検証（SignedGlobalIDのパース、有効期限確認、ユーザー照合）
3. トークンが有効な場合、Identityの更新とセッション再作成
4. トークンが無効な場合、エラー画面を表示

**関連システム・外部連携**：セッション管理システムとの連携（セッションの終了と新規作成）

**権限による制御**：未認証ユーザーもアクセス可能（allow_unauthenticated_access）。ただし、トークンに含まれるユーザー情報との照合が必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 43 | メールアドレス確認画面 | 主画面 | 新しいメールアドレスを確認（自動送信フォーム） |
| 44 | メールアドレス確認エラー画面 | エラー画面 | 無効なトークンの場合のエラー表示 |

## 機能種別

データ更新（認証連携）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| user_id | UUID | Yes | 対象ユーザーのID | Current.accountに所属するアクティブユーザー |
| email_address_token | String | Yes | メールアドレス変更確認用トークン | SignedGlobalIDとして有効であること |

### 入力データソース

- URLパラメータ：user_id
- POSTパラメータ：email_address_token（hidden field）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| リダイレクト先 | URL | 成功時はプロフィール編集画面、失敗時はエラー画面 |

### 出力先

- 成功時：プロフィール編集画面（edit_user_url）へリダイレクト
- 失敗時：invalid_token.html.erb画面を表示

## 処理フロー

### 処理シーケンス

```
1. ユーザーが確認メールのリンクをクリック
   └─ 確認画面（show）を表示

2. 確認画面が自動的にフォームを送信（auto-submit）
   └─ createアクションが実行

3. トークン検証処理
   └─ SignedGlobalID.parseでトークンをパース
   └─ old_email_addressとnew_email_addressを抽出
   └─ ユーザー照合と現在のメールアドレス確認

4. トークンが有効な場合
   └─ 新しいIdentityを取得または作成
   └─ ユーザーのIdentityを更新
   └─ 現在のセッションを終了
   └─ 新しいセッションを開始
   └─ プロフィール編集画面にリダイレクト

5. トークンが無効な場合
   └─ invalid_token画面を表示（422 Unprocessable Entity）
```

### フローチャート

```mermaid
flowchart TD
    A[開始: 確認リンククリック] --> B[確認画面表示 show]
    B --> C[自動フォーム送信]
    C --> D{レート制限チェック}
    D -->|制限超過| E[429エラー]
    D -->|OK| F[トークンパース]
    F --> G{トークン有効?}
    G -->|No: パース失敗| H[invalid_token画面表示]
    G -->|Yes| I{ユーザー照合OK?}
    I -->|No| H
    I -->|Yes| J{現在のメールアドレス一致?}
    J -->|No| H
    J -->|Yes| K[新しいIdentity取得/作成]
    K --> L[ユーザーのIdentity更新]
    L --> M[現在のセッション終了]
    M --> N[新しいセッション開始]
    N --> O[プロフィール画面にリダイレクト]
    O --> P[終了]
    H --> P
    E --> P
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-72-01 | トークン有効期限 | トークンは発行から30分間有効 | トークン検証時 |
| BR-72-02 | ユーザー照合 | トークンに含まれるユーザーと対象ユーザーが一致すること | トークン検証時 |
| BR-72-03 | 現在メールアドレス照合 | トークンに含まれるold_email_addressと現在のメールアドレスが一致すること | トークン検証時 |
| BR-72-04 | レート制限 | 1時間に5回までのリクエストに制限 | createアクション実行時 |
| BR-72-05 | セッション再作成 | メールアドレス変更成功時は既存セッションを終了し新規セッションを作成 | 変更成功時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ユーザー取得 | users | SELECT | アクティブユーザーの取得 |
| Identity取得/作成 | identities | SELECT/INSERT | 新しいメールアドレスでIdentity検索、なければ作成 |
| ユーザー更新 | users | UPDATE | identity_idを新しいIdentityに更新 |
| セッション終了 | sessions | DELETE | 現在のセッションを削除 |
| セッション作成 | sessions | INSERT | 新しいセッションを作成 |

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

#### users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, active | account_idとactive=trueで検索 | set_userで実行 |
| UPDATE | identity_id | 新しいIdentityのID | change_email_addressで実行 |

#### identities

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT/INSERT | email_address | 新しいメールアドレス | find_or_create_by! |

#### sessions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | id | 現在のセッションID | terminate_session |
| INSERT | identity_id | 新しいIdentityのID | start_new_session_for |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | トークン無効 | トークンが無効または期限切れ | invalid_token画面を表示、再度メールアドレス変更を促す |
| 429 | レート制限超過 | 1時間に5回以上のリクエスト | エラーページ表示 |
| 404 | ユーザー不明 | 指定されたユーザーが存在しないまたは無効 | 404エラー |

### リトライ仕様

トークンが期限切れの場合、新しいメールアドレス変更リクエストを行う必要がある

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

change_email_addressメソッド内でトランザクションが使用される：
- Identity取得/作成とユーザー更新が同一トランザクション内で実行
- いずれかの処理が失敗した場合はロールバック

## パフォーマンス要件

- レスポンス時間：2秒以内
- Identity作成時にユニーク制約違反が発生する可能性があるため、find_or_create_by!を使用

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

- SignedGlobalIDによるトークンの署名検証
- トークン有効期限（30分）による時間制限
- old_email_addressの照合による二重確認
- セッション再作成による認証状態のリフレッシュ
- レート制限（1時間5回）による不正アクセス防止
- 未認証アクセス許可だが、トークンによる本人確認が必須

## 備考

- 確認画面はauto-submitコントローラーにより自動的にフォーム送信される（ユーザー操作不要）
- Identity.find_or_create_by!により、既存のIdentityがあれば再利用、なければ新規作成
- メールアドレス変更後は全デバイスでの再ログインが必要になる可能性がある

---

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

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

### 推奨読解順序

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

メールアドレス確認に関連するデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | EmailAddressChangeable | `app/models/user/email_address_changeable.rb` | トークン検証ロジックの全体像 |
| 1-2 | Identity | `app/models/identity.rb` | メールアドレスを保持するモデル |

**読解のコツ**: `change_email_address_using_token`メソッドがトークン検証のエントリーポイント。SignedGlobalID.parseの戻り値から各種情報を取得する流れを追う。

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

処理の起点となるコントローラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ConfirmationsController | `app/controllers/users/email_addresses/confirmations_controller.rb` | show/createアクションの実装 |

**主要処理フロー**:
1. **2行目**: `allow_unauthenticated_access` - 未認証アクセスを許可
2. **5行目**: `rate_limit to: 5, within: 1.hour` - レート制限
3. **10-18行目**: createアクションでトークン検証と処理分岐
4. **11行目**: `change_email_address_using_token` - メールアドレス変更処理
5. **12-13行目**: セッション終了と新規セッション開始
6. **15行目**: プロフィール画面へリダイレクト
7. **17行目**: トークン無効時のエラー画面表示

#### Step 3: トークン検証ロジックを理解する

EmailAddressChangeableモジュールの検証処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | EmailAddressChangeable | `app/models/user/email_address_changeable.rb` | change_email_address_using_tokenメソッド |

**主要処理フロー**:
- **7-17行目**: `change_email_address_using_token` - トークン検証の本体
- **8行目**: SignedGlobalID.parseでトークンをパース
- **10-11行目**: old_email_addressとnew_email_addressを取得
- **13行目**: 各種検証（nil check、ユーザー照合、メールアドレス照合）
- **16行目**: 検証成功時にchange_email_addressを呼び出し
- **33-37行目**: `change_email_address` - 実際のIdentity更新処理

#### Step 4: ビューを理解する

画面表示の実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | show.html.erb | `app/views/users/email_addresses/confirmations/show.html.erb` | 確認画面（自動送信） |
| 4-2 | invalid_token.html.erb | `app/views/users/email_addresses/confirmations/invalid_token.html.erb` | エラー画面 |

**読解のコツ**: show.html.erbはdata-controller="auto-submit"により自動的にフォーム送信される。ユーザーは「Done」ボタンを押さなくても処理が進む。

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

```
Users::EmailAddresses::ConfirmationsController#create
    │
    ├─ @user.change_email_address_using_token(token)
    │      │
    │      ├─ SignedGlobalID.parse(token)
    │      │      └─ トークンのパースと署名検証
    │      │
    │      ├─ parsed_token.params.fetch("old_email_address")
    │      │
    │      ├─ parsed_token.params.fetch("new_email_address")
    │      │
    │      ├─ parsed_token.find（ユーザー照合）
    │      │
    │      └─ change_email_address(new_email_address)
    │             │
    │             ├─ Identity.find_or_create_by!(email_address: new_email_address)
    │             │
    │             └─ update!(identity: new_identity)
    │
    ├─ terminate_session（セッション終了）
    │
    └─ start_new_session_for @user.identity（新規セッション開始）
```

### データフロー図

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

email_address_token ───▶ ConfirmationsController#create ─┬─▶ プロフィール画面
                                │                         │   （成功時）
                                │                         │
                                ├──▶ トークンパース        └─▶ invalid_token画面
                                │                              （失敗時）
                                ├──▶ ユーザー照合
                                │
                                ├──▶ メールアドレス照合
                                │
                                ├──▶ Identity更新 ───────▶ identitiesテーブル
                                │
                                ├──▶ User更新 ───────────▶ usersテーブル
                                │
                                └──▶ セッション再作成 ───▶ sessionsテーブル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ConfirmationsController | `app/controllers/users/email_addresses/confirmations_controller.rb` | コントローラー | トークン検証とメールアドレス確定 |
| EmailAddressChangeable | `app/models/user/email_address_changeable.rb` | モデル（Concern） | トークン検証・メールアドレス変更ロジック |
| Identity | `app/models/identity.rb` | モデル | メールアドレス・認証情報管理 |
| show.html.erb | `app/views/users/email_addresses/confirmations/show.html.erb` | ビュー | 確認画面（自動送信） |
| invalid_token.html.erb | `app/views/users/email_addresses/confirmations/invalid_token.html.erb` | ビュー | エラー画面 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（22行目） |
