# 機能設計書 4-パスワードリカバリ

## 概要

本ドキュメントは、Shopware 6 Storefrontにおけるパスワードリカバリ機能の設計仕様を記述する。顧客がパスワードを忘れた場合にリカバリメールを送信する機能である。

### 本機能の処理概要

**業務上の目的・背景**：ECサイトの顧客がパスワードを忘れた場合、アカウントにアクセスできなくなる問題が発生する。本機能により、顧客は登録済みのメールアドレスにパスワードリセット用のリンクを送信してもらい、安全にパスワードを再設定できる。

**機能の利用シーン**：顧客がログイン時にパスワードを思い出せない場合、ログインページから「パスワードを忘れた方」リンクをクリックしてリカバリフォームにアクセスし、メールアドレスを入力してリカバリメールを送信する。

**主要な処理内容**：
1. パスワードリカバリフォームの表示
2. メールアドレスの入力とバリデーション
3. レート制限のチェック
4. 顧客の検索（active=true, guest=false）
5. 既存のリカバリレコードがあれば削除
6. 新しいリカバリハッシュを生成して保存
7. CustomerAccountRecoverRequestEventを発行（メール送信のトリガー）
8. 成功メッセージを表示

**関連システム・外部連携**：メール送信システム（CustomerAccountRecoverRequestEvent経由）

**権限による制御**：未ログイン状態の顧客が使用可能。ゲスト顧客は対象外。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 14 | パスワードリカバリページ | 主機能 | パスワード回復メールの送信（AuthController::recoverAccountForm） |
| 12 | ログイン/登録ページ | 遷移先機能 | パスワード忘れの場合のリカバリ画面への遷移 |

## 機能種別

認証処理 / メール送信

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| email[email] | string | Yes | 顧客のメールアドレス | Email形式 |
| email[storefrontUrl] | string | No | ストアフロントURL（自動設定） | Sales Channelのドメイン一覧に存在 |

### 入力データソース

- 画面入力（リカバリフォーム）
- POSTリクエストボディ

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| success | bool | 処理成功（常にtrue、セキュリティのため） |
| flashMessage | string | 成功メッセージ |

### 出力先

- 画面（フラッシュメッセージ）
- データベース（customer_recoveryテーブル）
- メール（CustomerAccountRecoverRequestEvent経由）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信（フォーム表示）
   └─ AuthController::recoverAccountForm がGETリクエストを受け取る
   └─ recover-password.html.twigを表示

2. リクエスト受信（フォーム送信）
   └─ AuthController::generateAccountRecovery がPOSTリクエストを受け取る

3. メールアドレス取得とバリデーション
   └─ email[email]を取得してDataBagに変換
   └─ storefrontUrlを設定

4. リカバリメール送信ルート呼び出し
   └─ SendPasswordRecoveryMailRoute::sendRecoveryMail を実行

5. バリデーション
   └─ メールアドレス形式チェック
   └─ storefrontUrl検証（validateStorefrontUrl=false時はスキップ）

6. レート制限チェック
   └─ email + IPアドレスでレート制限を確認

7. 顧客検索
   └─ active=true, guest=false, email一致で検索
   └─ boundSalesChannelIdもチェック

8. 既存リカバリレコード削除
   └─ 同一顧客の既存リカバリレコードを削除

9. 新規リカバリレコード作成
   └─ 32文字のランダムハッシュを生成
   └─ customer_recoveryテーブルにINSERT

10. リカバリURL生成
    └─ システム設定のURLテンプレートを使用
    └─ PasswordRecoveryUrlEventを発行

11. イベント発行
    └─ CustomerAccountRecoverRequestEventを発行（メール送信）

12. レスポンス生成
    └─ 成功メッセージを表示（顧客が見つからなくても同じ）
```

### フローチャート

```mermaid
flowchart TD
    A[リカバリ開始] --> B[メールアドレス入力]
    B --> C[バリデーション]
    C --> D{バリデーションOK?}
    D -->|No| E[エラー表示]
    D -->|Yes| F{レート制限OK?}
    F -->|No| G[待ち時間表示]
    F -->|Yes| H[顧客検索]
    H --> I{顧客存在?}
    I -->|No| J[成功メッセージ表示]
    I -->|Yes| K[既存リカバリ削除]
    K --> L[新規リカバリ作成]
    L --> M[リカバリURL生成]
    M --> N[CustomerAccountRecoverRequestEvent]
    N --> J
    E --> B
    G --> B
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | 顧客非開示 | 顧客が見つからなくても成功メッセージを表示（情報漏洩防止） | 常時 |
| BR-002 | ゲスト除外 | guest=trueの顧客はリカバリ対象外 | 顧客検索時 |
| BR-003 | アクティブ顧客のみ | active=trueの顧客のみリカバリ対象 | 顧客検索時 |
| BR-004 | 既存リカバリ削除 | 新規リカバリ作成前に既存のリカバリレコードを削除 | リカバリ作成時 |
| BR-005 | レート制限 | 同一email+IPからのリカバリ試行回数を制限 | リカバリ送信時 |
| BR-006 | ハッシュ形式 | リカバリハッシュは32文字のランダム英数字 | ハッシュ生成時 |

### 計算ロジック

- ハッシュ生成: `Random::getAlphanumericString(32)`
- リカバリURL: `storefrontUrl + urlTemplate.replace('%%RECOVERHASH%%', hash)`
- デフォルトURLテンプレート: `/account/recover/password?hash=%%RECOVERHASH%%`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 顧客検索 | customer | SELECT | メールアドレスで顧客を検索 |
| 既存リカバリ検索 | customer_recovery | SELECT | customerIdで既存リカバリを検索 |
| 既存リカバリ削除 | customer_recovery | DELETE | 既存のリカバリレコードを削除 |
| リカバリ作成 | customer_recovery | INSERT | 新規リカバリレコードを作成 |

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

#### customer

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, email, active, guest, bound_sales_channel_id | active=1, guest=0, email=入力値 | ORでboundSalesChannelId条件 |

#### customer_recovery

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, customer_id | customer_id = 顧客ID | 既存確認 |
| DELETE | - | id = 既存リカバリID | 既存削除 |
| INSERT | customer_id, hash | 顧客ID, 32文字ランダムハッシュ | 新規作成 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ConstraintViolationException | メールアドレス形式不正 | フォームにバリデーションエラー表示 |
| - | RateLimitExceededException | レート制限超過 | 待ち時間を表示 |
| - | InconsistentCriteriaIdsException | 内部エラー | デフォルトエラーメッセージ表示 |

### リトライ仕様

- レート制限超過時は、waitTime秒後に再試行可能

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

- 既存リカバリ削除と新規作成は個別トランザクション
- イベント発行はトランザクション外

## パフォーマンス要件

- リカバリ処理全体で1秒以内のレスポンスを目標
- メール送信は非同期で実行される場合がある

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

- 顧客が見つからなくても成功メッセージを表示（メールアドレス存在確認を防止）
- レート制限によるブルートフォース攻撃対策
- リカバリハッシュは32文字のランダム英数字で推測困難
- storefrontUrlはSales Channelのドメイン一覧に存在するもののみ許可

## 備考

- リカバリメールの実際の送信は CustomerAccountRecoverRequestEvent のリスナーが担当
- システム設定 `core.loginRegistration.pwdRecoverUrl` でURLテンプレートをカスタマイズ可能

---

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

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

### 推奨読解順序

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

リカバリに関連するエンティティとイベントの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | CustomerRecoveryEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerRecovery/CustomerRecoveryEntity.php` | hash, customerId等のプロパティ |
| 1-2 | CustomerAccountRecoverRequestEvent.php | `src/Core/Checkout/Customer/Event/CustomerAccountRecoverRequestEvent.php` | リカバリイベントの構造 |

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

StorefrontのリカバリリクエストはAuthControllerで受け付けられる。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AuthController.php | `src/Storefront/Controller/AuthController.php` | recoverAccountForm()（222-240行目）、generateAccountRecovery()（242-277行目） |

**主要処理フロー**:
1. **227-232行目**: HTTPキャッシュ設定（パフォーマンスフラグ有効時）
2. **235行目**: AccountLoginPageLoader::load呼び出し
3. **237-239行目**: recover-password.html.twigレンダリング
4. **249-254行目**: emailデータの取得とstorefrontUrl設定
5. **256-260行目**: sendPasswordRecoveryMailRoute呼び出し
6. **262-273行目**: 各種例外のキャッチとメッセージ設定
7. **276行目**: リカバリページへリダイレクト

#### Step 3: Store-APIルートを理解する

実際のリカバリロジックはSendPasswordRecoveryMailRouteに実装されている。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SendPasswordRecoveryMailRoute.php | `src/Core/Checkout/Customer/SalesChannel/SendPasswordRecoveryMailRoute.php` | sendRecoveryMail()メソッド（70-121行目） |

**主要処理フロー**:
- **73行目**: EmailIdnConverter::encodeDataBag
- **75行目**: validateRecoverEmail呼び出し
- **77-79行目**: レート制限チェック
- **81-85行目**: 顧客検索（見つからなくても成功レスポンス）
- **95-98行目**: 既存リカバリレコードの削除
- **100-105行目**: 新規リカバリレコード作成
- **114行目**: リカバリURL生成
- **116-118行目**: CustomerAccountRecoverRequestEvent発行

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

```
AuthController::generateAccountRecovery (Storefront)
    │
    └─ SendPasswordRecoveryMailRoute::sendRecoveryMail (Store-API)
           │
           ├─ EmailIdnConverter::encodeDataBag
           │
           ├─ SendPasswordRecoveryMailRoute::validateRecoverEmail
           │      └─ DataValidator::validate
           │
           ├─ RateLimiter::ensureAccepted
           │
           ├─ SendPasswordRecoveryMailRoute::getCustomerByEmail
           │      └─ CustomerRepository::search
           │
           ├─ SendPasswordRecoveryMailRoute::deleteRecoveryForCustomer
           │      └─ CustomerRecoveryRepository::delete
           │
           ├─ CustomerRecoveryRepository::create
           │
           ├─ SendPasswordRecoveryMailRoute::getRecoverUrl
           │      ├─ SystemConfigService::get
           │      └─ PasswordRecoveryUrlEvent (dispatch)
           │
           └─ CustomerAccountRecoverRequestEvent (dispatch)
```

### データフロー図

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

email ───▶ AuthController::generateAccountRecovery
               │
               ▼
         SendPasswordRecoveryMailRoute
               │
               ▼
         バリデーション + レート制限
               │
               ▼
         顧客検索 ───▶ [見つからない] ───▶ 成功レスポンス（セキュリティ）
               │
         [見つかった]
               │
               ▼
         既存リカバリ削除 ───▶ customer_recovery [DELETE]
               │
               ▼
         新規リカバリ作成 ───▶ customer_recovery [INSERT]
               │
               ▼
         リカバリURL生成
               │
               ▼
         CustomerAccountRecoverRequestEvent ───▶ [メール送信]
               │
               ▼
         成功メッセージ ───▶ [画面表示]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AuthController.php | `src/Storefront/Controller/AuthController.php` | コントローラ | リカバリフォーム表示・送信エンドポイント |
| SendPasswordRecoveryMailRoute.php | `src/Core/Checkout/Customer/SalesChannel/SendPasswordRecoveryMailRoute.php` | ルート | リカバリメール送信のStore-APIルート |
| AbstractSendPasswordRecoveryMailRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractSendPasswordRecoveryMailRoute.php` | 抽象クラス | リカバリルートの抽象定義 |
| CustomerRecoveryEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerRecovery/CustomerRecoveryEntity.php` | エンティティ | リカバリデータモデル |
| CustomerAccountRecoverRequestEvent.php | `src/Core/Checkout/Customer/Event/CustomerAccountRecoverRequestEvent.php` | イベント | リカバリリクエストイベント |
| PasswordRecoveryUrlEvent.php | `src/Core/Checkout/Customer/Event/PasswordRecoveryUrlEvent.php` | イベント | リカバリURLカスタマイズイベント |
| RateLimiter.php | `src/Core/Framework/RateLimiter/RateLimiter.php` | サービス | レート制限管理 |
