# 機能設計書 5-パスワードリセット

## 概要

本ドキュメントは、Shopware 6 Storefrontにおけるパスワードリセット機能の設計仕様を記述する。リカバリメールに記載されたリンクからパスワードを再設定する機能である。

### 本機能の処理概要

**業務上の目的・背景**：パスワードリカバリメールを受け取った顧客が、安全に新しいパスワードを設定できるようにする機能である。リカバリハッシュの有効期限を確認し、パスワードポリシーに従った新しいパスワードを設定する。

**機能の利用シーン**：顧客がパスワードリカバリメールに記載されたリンクをクリックし、新しいパスワードを入力してアカウントのパスワードを再設定する。

**主要な処理内容**：
1. リカバリハッシュを含むURLでリセットページを表示
2. ハッシュの有効性と有効期限を確認
3. 新しいパスワードと確認用パスワードの入力
4. パスワードポリシーのバリデーション
5. 顧客のパスワードを更新
6. リカバリレコードを削除
7. ログインとリカバリのレート制限をリセット
8. プロフィールページへリダイレクト

**関連システム・外部連携**：特になし

**権限による制御**：有効なリカバリハッシュを持つ顧客のみが使用可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 15 | パスワードリセットページ | 主機能 | 新しいパスワードの設定（AuthController::resetPasswordForm） |

## 機能種別

認証処理 / パスワード更新

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| hash | string | Yes | リカバリハッシュ（クエリパラメータ） | 存在確認 |
| password[hash] | string | Yes | リカバリハッシュ（フォーム） | - |
| password[newPassword] | string | Yes | 新しいパスワード | パスワードポリシー準拠 |
| password[newPasswordConfirm] | string | Yes | 確認用パスワード | newPasswordと一致 |

### 入力データソース

- クエリパラメータ（hash）
- POSTリクエストボディ（パスワードデータ）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| success | bool | 処理成功 |
| flashMessage | string | 成功/エラーメッセージ |

### 出力先

- 画面（フラッシュメッセージ）
- データベース（customerテーブルのpassword更新）
- HTTPレスポンス（リダイレクト）

## 処理フロー

### 処理シーケンス

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

2. ハッシュ取得と検証
   └─ クエリパラメータからhashを取得
   └─ hashがない場合はエラーメッセージを追加してリダイレクト

3. リカバリページデータ読み込み
   └─ AccountRecoverPasswordPageLoader::load でハッシュを検証
   └─ 顧客が見つからない場合はエラー

4. ハッシュ有効期限チェック
   └─ isHashExpired()で有効期限を確認
   └─ 期限切れの場合はエラーメッセージを追加してリダイレクト

5. フォーム表示
   └─ reset-password.html.twigをレンダリング

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

7. パスワードリセットルート呼び出し
   └─ ResetPasswordRoute::resetPassword を実行

8. バリデーション
   └─ パスワードポリシーチェック
   └─ newPassword と newPasswordConfirm の一致確認

9. ハッシュ有効期限再チェック
   └─ 2時間以内に作成されたハッシュのみ有効

10. 顧客パスワード更新
    └─ customerテーブルのpassword, legacyPassword, legacyEncoderを更新

11. リカバリレコード削除
    └─ customer_recoveryテーブルからレコードを削除

12. レート制限リセット
    └─ ログインとリカバリのレート制限をリセット

13. レスポンス生成
    └─ 成功メッセージを追加してプロフィールページへリダイレクト
```

### フローチャート

```mermaid
flowchart TD
    A[パスワードリセット開始] --> B{hashあり?}
    B -->|No| C[エラー: ハッシュなし]
    B -->|Yes| D[ページデータ読み込み]
    D --> E{顧客存在?}
    E -->|No| F[エラー: ハッシュ無効]
    E -->|Yes| G{ハッシュ有効期限OK?}
    G -->|No| H[エラー: ハッシュ期限切れ]
    G -->|Yes| I[リセットフォーム表示]
    I --> J[新パスワード入力]
    J --> K[ResetPasswordRoute::resetPassword]
    K --> L{バリデーションOK?}
    L -->|No| M[エラー表示]
    L -->|Yes| N{ハッシュ有効?}
    N -->|No| O[エラー: ハッシュ期限切れ]
    N -->|Yes| P[パスワード更新]
    P --> Q[リカバリレコード削除]
    Q --> R[レート制限リセット]
    R --> S[成功メッセージ]
    S --> T[プロフィールページへ]
    C --> U[リカバリページへ]
    F --> U
    H --> U
    M --> I
    O --> U
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | ハッシュ必須 | リセットにはリカバリハッシュが必須 | ページアクセス時 |
| BR-002 | ハッシュ有効期限 | リカバリハッシュは作成から2時間有効 | ハッシュ検証時 |
| BR-003 | パスワード一致 | newPasswordとnewPasswordConfirmが一致すること | バリデーション時 |
| BR-004 | パスワードポリシー | 新しいパスワードはパスワードポリシーに準拠すること | バリデーション時 |
| BR-005 | レガシーパスワード削除 | リセット時にlegacyPassword, legacyEncoderをnullに設定 | パスワード更新時 |
| BR-006 | レート制限リセット | パスワード変更成功時にログインとリカバリのレート制限をリセット | 更新成功時 |

### 計算ロジック

- ハッシュ有効期限: `createdAt + 2時間 > 現在時刻`
- 有効期限チェック: `(new DateTime())->sub(new DateInterval('PT2H')) < recovery.createdAt`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ハッシュ検証 | customer_recovery | SELECT | ハッシュでリカバリレコードを検索 |
| 顧客情報取得 | customer | SELECT | リカバリから顧客情報を取得 |
| パスワード更新 | customer | UPDATE | パスワードとレガシー情報を更新 |
| リカバリ削除 | customer_recovery | DELETE | 使用済みリカバリレコードを削除 |

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

#### customer_recovery

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, customer_id, hash, created_at | hash = 入力値 | ハッシュ検証 |
| DELETE | - | id = リカバリID | 使用済み削除 |

#### customer

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, email | customer_recovery経由 | 顧客取得 |
| UPDATE | password, legacy_password, legacy_encoder | 新パスワードハッシュ, null, null | パスワード更新 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | フラッシュメッセージ（DANGER） | hashパラメータなし | リカバリリクエストページへリダイレクト |
| CHECKOUT__CUSTOMER_NOT_FOUND_BY_HASH | CustomerNotFoundByHashException | ハッシュに対応する顧客なし | リカバリリクエストページへリダイレクト |
| CHECKOUT__CUSTOMER_RECOVERY_HASH_EXPIRED | CustomerRecoveryHashExpiredException | ハッシュ有効期限切れ | リカバリリクエストページへリダイレクト |
| - | ConstraintViolationException | パスワードバリデーションエラー | フォームにエラー表示 |

### リトライ仕様

- ハッシュ期限切れの場合は新たにリカバリメールを送信する必要あり

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

- パスワード更新とリカバリ削除は個別トランザクション

## パフォーマンス要件

- パスワードリセット処理全体で500ms以内のレスポンスを目標

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

- リカバリハッシュは32文字のランダム英数字で推測困難
- ハッシュは2時間で期限切れ
- パスワード変更成功後はレート制限をリセット（正当なユーザーのため）
- レガシーパスワード情報はリセット時に削除

## 備考

- パスワードポリシーは `core.loginRegistration.passwordMinLength` 等のシステム設定で定義
- リセット成功後はプロフィールページへリダイレクト（自動ログインはされない）

---

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

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

### 推奨読解順序

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

リセットに関連するエンティティとページの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AccountRecoverPasswordPage.php | `src/Storefront/Page/Account/RecoverPassword/AccountRecoverPasswordPage.php` | hash, isHashExpiredプロパティ |
| 1-2 | CustomerRecoveryEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerRecovery/CustomerRecoveryEntity.php` | hash, createdAtプロパティ |

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | AuthController.php | `src/Storefront/Controller/AuthController.php` | resetPasswordForm()（279-314行目）、resetPassword()（316-355行目） |

**主要処理フロー**:
1. **286-291行目**: hashパラメータの取得と検証
2. **294-300行目**: ページローダーでハッシュ検証、例外キャッチ
3. **304-308行目**: ハッシュ有効期限チェック
4. **310-313行目**: reset-password.html.twigレンダリング
5. **323-327行目**: passwordデータの取得とhash取得
6. **329-330行目**: resetPasswordRoute呼び出し
7. **333-352行目**: 各種例外のキャッチとエラーハンドリング

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

実際のリセットロジックはResetPasswordRouteに実装されている。

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

**主要処理フロー**:
- **63行目**: validateResetPassword呼び出し
- **67-69行目**: checkHashでハッシュ有効期限確認
- **71-78行目**: ハッシュで顧客リカバリを検索
- **87-92行目**: レート制限リセット
- **94-99行目**: パスワード更新（legacyPassword, legacyEncoder = null）
- **101-102行目**: customerRepository.update + deleteRecoveryForCustomer

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

```
AuthController::resetPasswordForm (Storefront - GET)
    │
    ├─ hashパラメータ検証
    │
    ├─ AccountRecoverPasswordPageLoader::load
    │      └─ CustomerRecoveryRepository::search
    │
    └─ renderStorefront('reset-password.html.twig')

AuthController::resetPassword (Storefront - POST)
    │
    └─ ResetPasswordRoute::resetPassword (Store-API)
           │
           ├─ ResetPasswordRoute::validateResetPassword
           │      ├─ PasswordValidationFactory::update
           │      └─ DataValidator::validate
           │
           ├─ ResetPasswordRoute::checkHash
           │      └─ CustomerRecoveryRepository::search
           │
           ├─ CustomerRecoveryRepository::search (顧客取得)
           │
           ├─ RateLimiter::reset (LOGIN_ROUTE)
           ├─ RateLimiter::reset (RESET_PASSWORD)
           │
           ├─ CustomerRepository::update (パスワード)
           │
           └─ ResetPasswordRoute::deleteRecoveryForCustomer
                  └─ CustomerRecoveryRepository::delete
```

### データフロー図

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

hash (GET) ───▶ AuthController::resetPasswordForm
                      │
                      ▼
                ページデータ読み込み
                      │
                ┌─────┴─────┐
                ▼           ▼
           ハッシュOK?  ハッシュ期限OK?
           (No→エラー)  (No→エラー)
                      │
                      ▼
                フォーム表示
                      │
newPassword,          ▼
newPasswordConfirm ───▶ AuthController::resetPassword
                      │
                      ▼
                ResetPasswordRoute
                      │
                      ▼
                バリデーション
                      │
                      ▼
                ハッシュ有効期限確認
                      │
                      ▼
                customer.password ───▶ [UPDATE]
                      │
                      ▼
                customer_recovery ───▶ [DELETE]
                      │
                      ▼
                レート制限リセット
                      │
                      ▼
                成功メッセージ ───▶ プロフィールページ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AuthController.php | `src/Storefront/Controller/AuthController.php` | コントローラ | リセットフォーム表示・処理エンドポイント |
| ResetPasswordRoute.php | `src/Core/Checkout/Customer/SalesChannel/ResetPasswordRoute.php` | ルート | パスワードリセットのStore-APIルート |
| AbstractResetPasswordRoute.php | `src/Core/Checkout/Customer/SalesChannel/AbstractResetPasswordRoute.php` | 抽象クラス | リセットルートの抽象定義 |
| AccountRecoverPasswordPageLoader.php | `src/Storefront/Page/Account/RecoverPassword/AccountRecoverPasswordPageLoader.php` | ローダー | リセットページデータの読み込み |
| AccountRecoverPasswordPage.php | `src/Storefront/Page/Account/RecoverPassword/AccountRecoverPasswordPage.php` | ページ | リセットページのデータモデル |
| CustomerRecoveryEntity.php | `src/Core/Checkout/Customer/Aggregate/CustomerRecovery/CustomerRecoveryEntity.php` | エンティティ | リカバリデータモデル |
