# 通知設計書 45-User Recovery Request

## 概要

本ドキュメントは、Shopwareにおける管理ユーザーのパスワードリカバリーリクエスト通知の仕様を定義する設計書である。管理画面のログインページでパスワードを忘れた管理ユーザーがリカバリーを要求した際に、パスワードリセットURLを含むメールを送信する機能について記載する。

### 本通知の処理概要

本通知は、管理画面（Administration）にログインできない管理ユーザーがパスワードリカバリーを要求した際に、パスワードリセット用のURLを含むメールを送信する機能である。顧客向けのパスワードリカバリー（customer.recovery.request）とは別に、管理ユーザー専用として実装されている。

**業務上の目的・背景**：管理ユーザーがパスワードを忘れた場合、店舗運営に支障をきたす。本機能により、管理ユーザーは他の管理者の助けを借りずに、自身でパスワードをリセットできる。セキュリティを維持しながら、ユーザビリティを確保するための重要な機能である。

**通知の送信タイミング**：管理画面のログインページで「パスワードを忘れた」リンクをクリックし、メールアドレスを入力して送信した直後に、UserRecoveryRequestEventが発火され、Flowビルダー経由でメール送信がトリガーされる。

**通知の受信者**：パスワードリカバリーを要求した管理ユーザー本人のメールアドレス。userテーブルに登録されたメールアドレスが使用される。

**通知内容の概要**：パスワードリセット用のURL（ハッシュ付き）、有効期限の案内、セキュリティに関する注意事項などが含まれる。

**期待されるアクション**：管理ユーザーはメール内のリセットURLをクリックし、新しいパスワードを設定する。

## 通知種別

メール通知

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（Flowビルダー経由） |
| 優先度 | 高（業務継続に影響） |
| リトライ | 有・3回 |

### 送信先決定ロジック

UserRecoveryRequestEventから取得される管理ユーザーのメールアドレスを使用。getMailStruct()メソッドでMailRecipientStructを生成し、ユーザーのemail、firstName、lastNameから受信者情報を構成する。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | システム設定 `core.basicInformation.email` |
| 送信元名称 | 販売チャネル設定のショップ名 |
| 件名 | Password recovery |
| 形式 | HTML/テキスト |

### 本文テンプレート

```twig
<div style="font-family:arial; font-size:12px;">
    <p>
        Dear {{ userRecovery.user.firstName }} {{ userRecovery.user.lastName }},<br/>
        <br/>
        You have requested to reset your password.<br/>
        <br/>
        Please click the following link to reset your password:<br/>
        <a href="{{ resetUrl }}">{{ resetUrl }}</a><br/>
        <br/>
        This link is valid for 2 hours.<br/>
        <br/>
        If you did not request this, please ignore this email.<br/>
    </p>
</div>
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| userRecovery | ユーザーリカバリーエンティティ | user_recovery テーブル | Yes |
| userRecovery.user | ユーザーエンティティ | user テーブル | Yes |
| userRecovery.user.firstName | ユーザーの名 | user.first_name | Yes |
| userRecovery.user.lastName | ユーザーの姓 | user.last_name | Yes |
| userRecovery.user.email | ユーザーのメールアドレス | user.email | Yes |
| resetUrl | パスワードリセットURL | 動的生成（ハッシュ付き） | Yes |
| salesChannel | 販売チャネル情報 | sales_channel テーブル | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API | POST /api/_action/user/user-recovery | ユーザー存在確認 | Administration API |
| Flowアクション | user.recovery.request | Flow条件一致 | UserRecoveryRequestEvent発火時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| ユーザー未存在 | 入力メールアドレスに対応するユーザーが存在しない場合（セキュリティ上、エラーは返さない） |
| 販売チャネル未存在 | 有効な販売チャネルが存在しない場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[管理ユーザーがリカバリー要求] --> B[UserRecoveryService::generateUserRecovery]
    B --> C{ユーザー存在?}
    C -->|No| D[処理終了（静かに終了）]
    C -->|Yes| E[既存リカバリー削除]
    E --> F[新規リカバリーレコード作成]
    F --> G[ハッシュ生成]
    G --> H[リセットURL生成]
    H --> I[販売チャネル取得]
    I --> J[SalesChannelContext生成]
    J --> K[UserRecoveryRequestEvent発火]
    K --> L[Flowビルダー処理]
    L --> M[SendMailAction実行]
    M --> N[メール送信]
    N --> O{送信結果}
    O -->|成功| P[終了]
    O -->|失敗| Q[エラーログ記録]
    Q --> P
    D --> P
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| user | ユーザー情報取得 | メールアドレスで検索 |
| user_recovery | リカバリー情報 | 既存レコード確認・新規作成 |
| sales_channel | 販売チャネル情報 | Context生成用 |
| mail_template | メールテンプレート | user.recovery.request |
| mail_template_type | テンプレートタイプ | technical_name = 'user.recovery.request' |

### テーブル別参照項目詳細

#### user

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | ユーザーID | email = 入力メールアドレス |
| email | メールアドレス | 受信者決定 |
| first_name | 名 | テンプレート変数 |
| last_name | 姓 | テンプレート変数 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| user_recovery | DELETE | 既存リカバリーレコード削除 |
| user_recovery | INSERT | 新規リカバリーレコード作成 |
| user | UPDATE | パスワード更新（リセット実行時） |

#### user_recovery

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT | id | UUID | 自動生成 |
| INSERT | user_id | ユーザーID | 対象ユーザー |
| INSERT | hash | ランダム32文字 | Random::getAlphanumericString(32) |
| INSERT | created_at | 現在日時 | 有効期限判定用 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ユーザー未存在 | メールアドレスに対応するユーザーなし | 静かに終了（セキュリティ考慮） |
| 販売チャネル未存在 | 有効な販売チャネルなし | UserException::salesChannelNotFound |
| 送信失敗 | SMTPサーバーエラー | リトライ後、エラーログ記録 |
| テンプレートエラー | Twigレンダリング失敗 | MailErrorEvent発火 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 3回 |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的な送信エラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | システム設定に依存 |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（パスワードリカバリーの緊急性を考慮）

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

- **ユーザー列挙防止**：存在しないメールアドレスでもエラーを返さず静かに終了
- **リセットURL有効期限**：2時間で失効（checkHash()メソッドで検証）
- **ハッシュの一意性**：Random::getAlphanumericString(32)で十分なエントロピー確保
- **既存リカバリー削除**：新規リカバリー発行時に既存を削除し、有効なリンクを1つに限定
- **HTTPS必須**：リセットURLはHTTPSで生成される

## 備考

- MailTemplateTypes::MAILTYPE_USER_RECOVERY_REQUEST 定数で定義（user.recovery.request）
- 顧客向けのcustomer.recovery.requestとは別の実装
- リセットURLは管理画面（/admin#/login/user-recovery/{hash}）へのリンク
- URLが生成できない場合はAPP_URL環境変数を使用したフォールバック

---

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

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

### 推奨読解順序

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

ユーザーリカバリーイベントクラスを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | UserRecoveryRequestEvent.php | `src/Core/System/User/Recovery/UserRecoveryRequestEvent.php` | イベント名 user.recovery.request（24行目）、実装インターフェース（22行目） |

**読解のコツ**: UserRecoveryRequestEventはUserAware, MailAware, FlowEventAwareを実装。userRecoveryエンティティとresetUrlを保持し、テンプレート変数として提供する。

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

パスワードリカバリー処理のサービスクラスを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | UserRecoveryService.php | `src/Core/System/User/Recovery/UserRecoveryService.php` | generateUserRecovery()メソッド（49行目〜）がエントリーポイント |

**主要処理フロー**:
1. **51行目**: getUserByEmail() - メールアドレスでユーザー検索
2. **53-55行目**: ユーザー未存在時は静かにreturn
3. **63-65行目**: 既存リカバリーの削除（deleteRecoveryForUser）
4. **67-70行目**: 新規リカバリーレコード作成（hash生成）
5. **74-78行目**: リカバリーレコード取得
6. **82-88行目**: リセットURL生成（administration.indexルート + ハッシュ）
7. **91-103行目**: SalesChannelContext生成
8. **105-108行目**: UserRecoveryRequestEvent発火

#### Step 3: リセットURL検証を理解する

ハッシュ検証とパスワード更新処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | UserRecoveryService.php | `src/Core/System/User/Recovery/UserRecoveryService.php` | checkHash()（111行目〜）、updatePassword()（125行目〜） |

**主要処理フロー**:
- **111-123行目**: ハッシュ検証（2時間以内かチェック）
- **125-147行目**: パスワード更新処理

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

```
Administration Login Page (Forgot Password)
    │
    └─ API: POST /api/_action/user/user-recovery
           │
           └─ UserRecoveryService::generateUserRecovery()
                  │
                  ├─ getUserByEmail() - ユーザー検索
                  │
                  ├─ deleteRecoveryForUser() - 既存削除
                  │
                  ├─ userRecoveryRepo.create() - 新規作成
                  │
                  ├─ getUserRecovery() - レコード取得
                  │
                  ├─ router.generate() - URL生成
                  │      └─ 'administration.index' + hash
                  │
                  ├─ getSalesChannel() - 販売チャネル取得
                  │
                  ├─ salesChannelContextService.get() - Context生成
                  │
                  └─ EventDispatcher::dispatch(UserRecoveryRequestEvent)
                         │
                         └─ Flow Engine
                                │
                                └─ SendMailAction::handleFlow()
                                       │
                                       └─ MailService::send()
```

### データフロー図

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

POST /api/_action/              UserRecoveryService                 UserRecoveryRequestEvent
  user/user-recovery                │                                    │
  │                                 ├─ ユーザー検索                       ├─ user.recovery.request イベント
  └─ email                          ├─ 既存リカバリー削除                 │
                                    ├─ 新規リカバリー作成                  ▼
                                    │     └─ hash: 32文字               Flow Engine
                                    │                                      │
                                    ├─ リセットURL生成                     ▼
                                    │     └─ /admin#/.../hash          SendMailAction
                                    │                                      │
                                    └─ イベント発火                        ▼
                                                                    Password Recovery Email
                                                                    (to: 管理ユーザー)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| UserRecoveryRequestEvent.php | `src/Core/System/User/Recovery/UserRecoveryRequestEvent.php` | ソース | リカバリーリクエストイベント |
| UserRecoveryService.php | `src/Core/System/User/Recovery/UserRecoveryService.php` | ソース | リカバリーサービス |
| MailTemplateTypes.php | `src/Core/Content/MailTemplate/MailTemplateTypes.php` | ソース | MAILTYPE_USER_RECOVERY_REQUEST定数（30行目） |
| SendMailAction.php | `src/Core/Content/Flow/Dispatching/Action/SendMailAction.php` | ソース | Flowメール送信アクション |
| MailService.php | `src/Core/Content/Mail/Service/MailService.php` | ソース | メール送信サービス |
| Migration1562240231UserPasswordRecovery.php | `src/Core/Migration/V6_3/Migration1562240231UserPasswordRecovery.php` | マイグレーション | user_recoveryテーブル作成 |
