# 機能設計書 63-招待管理

## 概要

本ドキュメントは、Medusaコマースプラットフォームにおける管理者ユーザー招待の管理機能の設計を記述する。招待の作成、一覧取得、再送信、受諾、削除を提供する。

### 本機能の処理概要

**業務上の目的・背景**：新しい管理者スタッフをシステムに追加する際、セキュリティを確保しながらスムーズにアカウントを作成するために招待機能が必要である。招待機能により、管理者が指定したメールアドレスに招待トークン付きのリンクを送信し、受信者がそのリンクから自身のアカウントを設定できる。これにより、不正なユーザー登録を防ぎつつ、組織的なユーザー管理が可能となる。

**機能の利用シーン**：
- 新規管理者スタッフの招待（メールアドレス入力で招待作成）
- 招待一覧画面での招待状況確認
- 招待メール再送信（トークン更新）
- 招待受諾によるユーザーアカウント作成
- 未使用招待の削除

**主要な処理内容**：
1. 招待作成（JWTトークン生成、有効期限設定）
2. 招待一覧取得（フィルタ・ページネーション対応）
3. 招待トークンのリフレッシュ（再送信時）
4. 招待受諾処理（トークン検証、ユーザー作成、招待削除）
5. 招待削除

**関連システム・外部連携**：
- 通知モジュール（Notification Module）経由で招待メール送信
- 認証モジュール（Auth Module）との連携でユーザー認証設定
- RBACモジュールとの連携で招待時のロール指定

**権限による制御**：招待作成・管理は認証済み管理者のみ可能。招待受諾時は、招待トークンが有効であり、かつ未認証状態（既存アカウントを持たない）であることが必要。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 3 | 招待画面 | 主画面 | 招待トークンの検証とアカウント設定 |
| 120 | ユーザー招待画面 | 主画面 | 新規ユーザーの招待 |

## 機能種別

CRUD操作 / トークン管理

## 入力仕様

### 入力パラメータ

#### 招待作成

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| email | string | Yes | 招待先メールアドレス | 有効なメール形式、既存ユーザーでないこと |
| metadata | object | No | メタデータ | - |

#### 招待受諾

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| token | string | Yes | 招待トークン（クエリパラメータ） | 有効なJWTトークン |
| auth_identity_id | string | Yes | 認証アイデンティティID | 有効なauth_identity |
| first_name | string | No | 名 | - |
| last_name | string | No | 姓 | - |

#### 一覧取得

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| q | string | No | 検索クエリ | - |
| email | string[] | No | メールアドレスフィルタ | - |
| limit | number | No | 取得件数 | 1以上 |
| offset | number | No | オフセット | 0以上 |

### 入力データソース

- Admin API HTTPリクエスト
- 招待メール内のリンク（トークン付き）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | 招待ID（invite_プレフィックス） |
| email | string | 招待先メールアドレス |
| accepted | boolean | 受諾済みフラグ |
| token | string | JWTトークン |
| expires_at | Date | 有効期限 |
| metadata | object | null | メタデータ |
| created_at | Date | 作成日時 |
| updated_at | Date | 更新日時 |
| deleted_at | Date | null | 削除日時 |

### 出力先

- Admin API HTTPレスポンス
- イベント発行（invite.created, invite.token_generated, invite.accepted, invite.deleted）

## 処理フロー

### 処理シーケンス

```
1. 招待作成
   ├─ 既存ユーザーチェック（同一メール）
   ├─ JWTトークン生成（id, email含む）
   ├─ 有効期限設定（デフォルト24時間）
   ├─ 招待レコード作成
   └─ INVITE_TOKEN_GENERATEDイベント発行

2. 招待一覧取得
   └─ remoteQueryでinviteテーブル検索

3. トークンリフレッシュ（再送信）
   ├─ 招待存在確認
   ├─ 新規トークン生成
   ├─ 有効期限更新
   └─ INVITE_TOKEN_GENERATEDイベント発行

4. 招待受諾
   ├─ トークン検証（validateTokenStep）
   ├─ 招待に紐づくロール取得
   ├─ ユーザー作成（createUsersWorkflow）
   ├─ 認証アイデンティティ紐付け（setAuthAppMetadataStep）
   ├─ 招待削除（deleteInvitesStep）
   └─ INVITE_ACCEPTEDイベント発行

5. 招待削除
   └─ deleteInvitesWorkflow実行
```

### フローチャート

```mermaid
flowchart TD
    A[API リクエスト] --> B{操作種別}
    B -->|POST /invites| C[招待作成]
    B -->|GET /invites| D[招待一覧取得]
    B -->|POST /invites/:id/resend| E[トークンリフレッシュ]
    B -->|POST /invites/accept| F[招待受諾]
    B -->|DELETE /invites/:id| G[招待削除]

    C --> H{既存ユーザー?}
    H -->|Yes| I[エラー返却]
    H -->|No| J[トークン生成]
    J --> K[招待作成]
    K --> L[イベント発行]

    F --> M[トークン検証]
    M --> N{有効?}
    N -->|No| O[401エラー]
    N -->|Yes| P[ロール取得]
    P --> Q[ユーザー作成]
    Q --> R[認証紐付け]
    R --> S[招待削除]

    D --> T[remoteQuery実行]
    E --> U[トークン更新]
    G --> V[softDelete実行]

    L --> W[レスポンス返却]
    S --> W
    T --> W
    U --> W
    V --> W
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-63-01 | 既存ユーザー招待禁止 | 既にユーザーアカウントが存在するメールアドレスには招待できない | 招待作成時 |
| BR-63-02 | トークン有効期限 | 招待トークンはデフォルト24時間（86400秒）で期限切れ | 招待作成・リフレッシュ時 |
| BR-63-03 | 認証済みユーザー受諾禁止 | 既に認証済み（actor_idあり）のユーザーは招待を受諾できない | 招待受諾時 |
| BR-63-04 | 受諾時招待削除 | 招待受諾後、招待レコードは自動削除 | 招待受諾完了時 |
| BR-63-05 | ロール継承 | 招待に設定されたロールは、作成されるユーザーに継承される | 招待受諾時 |

### 計算ロジック

**有効期限計算**:
```typescript
expiresAt = currentTime + (jwtOptions.expiresIn || 86400) // デフォルト24時間
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 招待作成 | invite | INSERT | 新規招待レコード作成 |
| 既存ユーザーチェック | user | SELECT | メールアドレスで検索 |
| 一覧取得 | invite | SELECT | フィルタ・ページネーション付き |
| トークン更新 | invite | UPDATE | token, expires_at更新 |
| 招待削除 | invite | UPDATE | deleted_at設定（ソフトデリート） |

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

#### invite

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成（invite_プレフィックス） | generateEntityIdで生成 |
| INSERT | email | 入力値 | ユニーク制約あり |
| INSERT | accepted | false | デフォルト値 |
| INSERT | token | JWTトークン | generateToken関数で生成 |
| INSERT | expires_at | 有効期限日時 | getExpiresAt関数で計算 |
| UPDATE | token | 新規JWTトークン | リフレッシュ時 |
| UPDATE | expires_at | 新規有効期限 | リフレッシュ時 |
| UPDATE | deleted_at | 現在日時 | ソフトデリート時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| INVALID_DATA | MedusaError | 既存ユーザーのメールアドレスで招待作成 | 400レスポンス、エラーメッセージで該当メール表示 |
| INVALID_DATA | MedusaError | 存在しない招待IDでリフレッシュ | 400レスポンス |
| INVALID_DATA | MedusaError | 招待トークンが期限切れ | 400レスポンス |
| INVALID_DATA | MedusaError | 認証済みユーザーが招待受諾 | 400レスポンス |
| UNAUTHORIZED | MedusaError | 無効なトークン | 401レスポンス |

### リトライ仕様

招待メール送信失敗時は、手動で「再送信」機能を使用してリトライ可能。

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

- 招待作成・受諾はワークフロー内でトランザクション管理
- @InjectTransactionManagerデコレータで内部トランザクションを制御
- 招待受諾時は、ユーザー作成・認証紐付け・招待削除が一貫して実行

## パフォーマンス要件

- emailカラムにユニークインデックス（deleted_at IS NULL条件付き）
- tokenカラムにインデックス（検索高速化）

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

- JWTトークンには秘密鍵による署名
- トークンには招待IDとメールアドレスのみ含む（機密情報は含まない）
- 有効期限による時限的アクセス制御
- 招待受諾後はトークン無効化（招待削除）
- JWT秘密鍵はConfigModuleで環境変数から取得

## 備考

- 招待IDはinvite_プレフィックス付きで自動生成
- トークン有効期限はjwtOptions.expiresInまたはvalid_durationで設定可能
- 招待受諾時に認証アイデンティティが必要（事前にレジストレーションAPIで作成）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | invite.ts | `packages/modules/user/src/models/invite.ts` | Inviteモデルの構造（id, email, accepted, token, expires_at, metadata） |

**読解のコツ**: `model.define`で定義。emailとtokenにインデックスが設定されている点に注目。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | route.ts | `packages/medusa/src/api/admin/invites/route.ts` | GET - 招待一覧、POST - 招待作成 |
| 2-2 | accept/route.ts | `packages/medusa/src/api/admin/invites/accept/route.ts` | POST - 招待受諾処理 |
| 2-3 | [id]/resend/route.ts | `packages/medusa/src/api/admin/invites/[id]/resend/route.ts` | POST - トークンリフレッシュ |

**主要処理フロー**:
1. **route.ts 14-36行目**: 招待一覧取得
2. **route.ts 38-62行目**: 招待作成、createInvitesWorkflow使用
3. **accept/route.ts 10-38行目**: 招待受諾、認証チェック、acceptInviteWorkflow使用

#### Step 3: サービス層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | user-module.ts | `packages/modules/user/src/services/user-module.ts` | validateInviteToken, createInvites_, refreshInviteTokens_の実装 |

**主要処理フロー**:
- **98-133行目**: validateInviteToken - JWT検証、有効期限チェック
- **294-325行目**: createInvites_ - 既存ユーザーチェック、トークン生成
- **160-195行目**: refreshInviteTokens_ - トークン更新

#### Step 4: ワークフローを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | accept-invite.ts | `packages/core/core-flows/src/invite/workflows/accept-invite.ts` | 招待受諾ワークフロー全体フロー |
| 4-2 | validate-token.ts | `packages/core/core-flows/src/invite/steps/validate-token.ts` | トークン検証ステップ |

**主要処理フロー**:
- **accept-invite.ts 46-96行目**: トークン検証→ロール取得→ユーザー作成→認証紐付け→招待削除→イベント発行

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

```
Admin API Route
    │
    ├─ POST /invites
    │      └─ createInvitesWorkflow
    │             ├─ createInvitesStep
    │             │      └─ inviteService_.create
    │             └─ emitEventStep (INVITE_TOKEN_GENERATED)
    │
    ├─ POST /invites/accept
    │      └─ acceptInviteWorkflow
    │             ├─ validateTokenStep
    │             │      └─ userModuleService.validateInviteToken
    │             ├─ getInviteRolesStep
    │             ├─ createUsersWorkflow (nested)
    │             ├─ setAuthAppMetadataStep
    │             ├─ deleteInvitesStep
    │             └─ emitEventStep (INVITE_ACCEPTED)
    │
    └─ POST /invites/:id/resend
           └─ refreshInviteTokensWorkflow
                  └─ refreshInviteTokensStep
                         └─ userModuleService.refreshInviteTokens
```

### データフロー図

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

招待作成
  email ─────────────▶ createInvites_ ────────────▶ Invite record
                            │                              │
                            ▼                              ▼
                       JWTトークン生成                  通知イベント発行
                                                     (INVITE_TOKEN_GENERATED)

招待受諾
  token ─────────────▶ validateInviteToken ────────▶ 有効な招待情報
  auth_identity_id         │
  user_info                ▼
                     createUsersWorkflow ─────────▶ User record
                            │
                            ▼
                     setAuthAppMetadataStep ──────▶ 認証紐付け
                            │
                            ▼
                     deleteInvitesStep ───────────▶ 招待削除
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| invite.ts | `packages/modules/user/src/models/invite.ts` | ソース | Inviteモデル定義 |
| user-module.ts | `packages/modules/user/src/services/user-module.ts` | ソース | UserModuleService（招待関連メソッド含む） |
| route.ts | `packages/medusa/src/api/admin/invites/route.ts` | ソース | 招待一覧・作成API |
| accept/route.ts | `packages/medusa/src/api/admin/invites/accept/route.ts` | ソース | 招待受諾API |
| [id]/resend/route.ts | `packages/medusa/src/api/admin/invites/[id]/resend/route.ts` | ソース | トークンリフレッシュAPI |
| accept-invite.ts | `packages/core/core-flows/src/invite/workflows/accept-invite.ts` | ソース | 招待受諾ワークフロー |
| create-invites.ts | `packages/core/core-flows/src/invite/workflows/create-invites.ts` | ソース | 招待作成ワークフロー |
| validate-token.ts | `packages/core/core-flows/src/invite/steps/validate-token.ts` | ソース | トークン検証ステップ |
| utils.ts | `packages/modules/user/src/utils/utils.ts` | ソース | getExpiresAt関数 |
