# 機能設計書 1-サインアップ

## 概要

本ドキュメントは、Fizzyアプリケーションにおけるサインアップ機能の設計書である。メールアドレスを入力して新規アカウントを作成する機能で、パスワードレス認証（マジックリンク認証）を採用している。

### 本機能の処理概要

**業務上の目的・背景**：従来のパスワードベース認証は、パスワードの記憶負担、漏洩リスク、リセット対応などの課題がある。マジックリンク認証を採用することで、ユーザーはパスワードを管理する必要がなく、メールアドレスのみで安全にアカウントを作成できる。これにより、ユーザー体験の向上とセキュリティの両立を実現している。

**機能の利用シーン**：新規ユーザーがFizzyを初めて利用する際に、サインアップ画面からメールアドレスを入力してアカウント作成を開始する。メールで届くマジックリンクのコードを入力後、氏名を登録してアカウント作成を完了する。

**主要な処理内容**：
1. サインアップ画面でメールアドレスを入力
2. 入力されたメールアドレスのバリデーション（形式チェック）
3. Identityレコードの作成または既存レコードの取得
4. マジックリンク（6桁コード）の生成と有効期限（15分）の設定
5. マジックリンクコードを含むメールの送信
6. コード入力画面でのマジックリンク検証
7. サインアップ完了画面での氏名入力
8. アカウント・ユーザーレコードの作成とカスタマーテンプレートのセットアップ

**関連システム・外部連携**：メール送信にはRailsのAction Mailerを使用し、非同期処理（Active Job）で送信される。

**権限による制御**：サインアップは未認証ユーザーのみが実行可能。既に認証済みのユーザーがアクセスした場合は、サインアップ完了画面にリダイレクトされる。また、テナント制限が有効な場合（Account.accepting_signups?がfalse）は、サインイン画面にリダイレクトされる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | ログイン画面 | 参照画面 | 新規ユーザーの場合はサインアップ処理へ遷移 |
| 2 | サインアップ画面 | 主画面 | 新規アカウント作成のためのメールアドレス入力とマジックリンク送信 |
| 3 | サインアップ完了画面 | 結果表示画面 | サインアップ完了後の確認表示 |
| 4 | マジックリンク送信完了画面 | 参照画面 | マジックリンク送信後の確認画面表示 |

## 機能種別

CRUD操作 / データ作成 / 認証処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| email_address | String | Yes | ユーザーのメールアドレス | URI::MailTo::EMAIL_REGEXPに準拠した形式チェック |
| full_name | String | Yes（完了時） | ユーザーの氏名 | 必須、最大240文字 |
| code | String | Yes（認証時） | マジックリンクコード | 6桁の英数字 |

### 入力データソース

- 画面入力（サインアップフォーム）
- 画面入力（サインアップ完了フォーム）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| identity | Identity | 作成されたIdentityレコード |
| magic_link | MagicLink | 生成されたマジックリンクレコード |
| account | Account | 作成されたアカウントレコード |
| user | User | 作成されたユーザーレコード（owner権限） |
| session | Session | 作成されたセッションレコード |

### 出力先

- データベース（identities, magic_links, accounts, users, sessions, accessesテーブル）
- メール送信（マジックリンクコードを含むメール）
- Cookie（セッショントークン）

## 処理フロー

### 処理シーケンス

```
1. サインアップ画面表示（SignupsController#new）
   └─ Signup.newでフォームオブジェクトを初期化
2. メールアドレス送信（SignupsController#create）
   └─ Signup#valid?(:identity_creation)でバリデーション
   └─ Signup#create_identityでIdentity作成とマジックリンク送信
3. マジックリンク画面表示（Sessions::MagicLinksController#show）
   └─ コード入力フォームを表示
4. マジックリンク検証（Sessions::MagicLinksController#create）
   └─ MagicLink.consume(code)でコード検証
   └─ start_new_session_for(identity)でセッション開始
5. サインアップ完了画面表示（Signups::CompletionsController#new）
   └─ Signup.new(identity: Current.identity)でフォーム初期化
6. アカウント作成（Signups::CompletionsController#create）
   └─ Signup#completeでAccount, User作成
   └─ setup_customer_templateでテンプレートセットアップ
```

### フローチャート

```mermaid
flowchart TD
    A[サインアップ画面] --> B[メールアドレス入力]
    B --> C{バリデーション}
    C -->|NG| D[エラー表示]
    D --> B
    C -->|OK| E[Identity作成/取得]
    E --> F[MagicLink生成]
    F --> G[メール送信]
    G --> H[コード入力画面]
    H --> I[コード入力]
    I --> J{コード検証}
    J -->|NG| K[エラー表示]
    K --> I
    J -->|OK| L[セッション開始]
    L --> M[サインアップ完了画面]
    M --> N[氏名入力]
    N --> O{バリデーション}
    O -->|NG| P[エラー表示]
    P --> N
    O -->|OK| Q[Account作成]
    Q --> R[User作成]
    R --> S[テンプレートセットアップ]
    S --> T[ランディング画面へリダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | メールアドレス形式 | RFC準拠のメールアドレス形式であること | メールアドレス入力時 |
| BR-002 | マジックリンク有効期限 | 生成から15分間有効 | コード検証時 |
| BR-003 | マジックリンク一意性 | コードはシステム全体で一意 | コード生成時 |
| BR-004 | レート制限 | 3分間に10回まで | サインアップ送信時 |
| BR-005 | テナント制限 | Account.accepting_signups?がtrueの場合のみ許可 | サインアップアクセス時 |
| BR-006 | 氏名最大長 | 240文字以内 | サインアップ完了時 |

### 計算ロジック

- マジックリンクコード生成: 6桁のランダムな英数字（Code.generate(6)）
- 有効期限: 現在時刻 + 15分（EXPIRATION_TIME.from_now）
- アカウント名自動生成: AccountNameGeneratorにより、IdentityとNameから生成

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| Identity作成 | identities | INSERT/SELECT | メールアドレスでIdentityを作成または取得 |
| MagicLink作成 | magic_links | INSERT | 6桁コードと有効期限を持つレコード作成 |
| MagicLink消費 | magic_links | DELETE | コード検証成功時にレコード削除 |
| Session作成 | sessions | INSERT | 認証成功時にセッションレコード作成 |
| Account作成 | accounts | INSERT | サインアップ完了時にアカウント作成 |
| User作成 | users | INSERT | システムユーザーとオーナーユーザーを作成 |
| JoinCode作成 | account_join_codes | INSERT | アカウント作成後に参加コード自動生成 |

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

#### identities

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/SELECT | email_address | 入力されたメールアドレス（正規化済み） | find_or_create_by!で重複回避 |

#### magic_links

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | identity_id | 関連するIdentityのID | |
| INSERT | code | 生成された6桁コード | ユニーク制約あり |
| INSERT | purpose | "sign_up" | サインアップ用途 |
| INSERT | expires_at | 現在時刻 + 15分 | |
| DELETE | - | code一致かつexpires_at > 現在時刻 | consume時に削除 |

#### accounts

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name | 自動生成されたアカウント名 | |
| INSERT | external_account_id | ExternalIdSequence.next | 自動採番 |

#### users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | 作成されたアカウントID | |
| INSERT | identity_id | 関連するIdentityのID | オーナーユーザーのみ |
| INSERT | name | "System" または 入力された氏名 | |
| INSERT | role | :system または :owner | |
| INSERT | verified_at | 現在時刻 | オーナーユーザーのみ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | バリデーションエラー | メールアドレス形式不正 | エラーメッセージ表示 |
| 422 | バリデーションエラー | 氏名未入力/長すぎる | エラーメッセージ表示 |
| 429 | レート制限超過 | 3分間に10回超過 | "Try again later."メッセージ表示 |
| 401 | 認証エラー | 無効なマジックリンクコード | エラーアニメーション表示 |
| 302 | リダイレクト | テナント制限で拒否 | サインイン画面へリダイレクト |

### リトライ仕様

- マジックリンクコード入力: 15分間に10回まで（レート制限）
- 無効なコードの場合は再入力可能（同じコード入力画面に戻る）

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

- Identity作成: 単一レコード操作のため自動トランザクション
- Account作成（Signup#complete）: Account, Systemユーザー, Ownerユーザー作成を単一トランザクションで実行
- エラー発生時: @account&.destroy!で作成済みデータをロールバック

## パフォーマンス要件

- マジックリンクメール送信: 非同期処理（deliver_later）によりレスポンス即座返却
- レート制限: 3分間10リクエストでDoS攻撃防止

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

- マジックリンクコード: 6桁のランダム英数字で予測困難
- 有効期限: 15分間の短期間で漏洩リスク軽減
- コード消費: 検証成功時に即座に削除（再利用不可）
- メールアドレス正規化: strip.downcase.presenceで正規化し、タイミング攻撃を防止
- secure_compare使用: メールアドレス照合にタイミング攻撃対策
- レート制限: ブルートフォース攻撃防止

## 備考

- マジックリンクはサインインとサインアップで共通のMagicLinkモデルを使用し、purpose属性で区別
- 既存ユーザーがサインアップ画面を使用した場合もIdentityは重複作成されない（find_or_create_by!）
- 開発環境ではマジックリンクコードが画面に表示される（デバッグ用）

---

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

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

### 推奨読解順序

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

まず、サインアップ処理で扱うデータモデルの構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | signup.rb | `app/models/signup.rb` | フォームオブジェクトの構造、バリデーションルール、create_identityとcompleteメソッドの役割 |
| 1-2 | identity.rb | `app/models/identity.rb` | ユーザー認証の基盤となるモデル、send_magic_linkメソッドの実装 |
| 1-3 | magic_link.rb | `app/models/magic_link.rb` | マジックリンクの生成・検証ロジック、有効期限管理 |
| 1-4 | account.rb | `app/models/account.rb` | アカウントモデル、create_with_ownerクラスメソッド |

**読解のコツ**: Signupはフォームオブジェクトパターンを使用しており、ActiveModel::Modelをincludeしている。データベースに直接対応するテーブルは持たないが、複数モデルの作成を調整する役割を担う。

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

処理の起点となるコントローラーを特定し、リクエストの流れを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | signups_controller.rb | `app/controllers/signups_controller.rb` | サインアップ開始のエントリーポイント、レート制限、テナント制限 |
| 2-2 | magic_links_controller.rb | `app/controllers/sessions/magic_links_controller.rb` | マジックリンク検証、セッション開始処理 |
| 2-3 | completions_controller.rb | `app/controllers/signups/completions_controller.rb` | サインアップ完了、アカウント作成処理 |

**主要処理フロー**:
1. **4-5行目（signups_controller.rb）**: レート制限（3分間10回）とテナント制限の設定
2. **14-20行目（signups_controller.rb）**: メールアドレスバリデーションとマジックリンク送信
3. **12-17行目（magic_links_controller.rb）**: マジックリンクコードの消費と認証処理
4. **10-17行目（completions_controller.rb）**: Signup#completeでアカウント作成

#### Step 3: 認証基盤を理解する

セッション管理と認証の仕組みを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | authentication.rb | `app/controllers/concerns/authentication.rb` | 認証モジュール、セッション管理、start_new_session_forメソッド |
| 3-2 | session.rb | `app/models/session.rb` | セッションモデルの構造 |

**主要処理フロー**:
- **88-91行目（authentication.rb）**: start_new_session_forでセッション作成とCookie設定
- **94-97行目（authentication.rb）**: set_current_sessionでCookieにセッショントークン保存

#### Step 4: メール送信を理解する

マジックリンクメールの送信処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | magic_link_mailer.rb | `app/mailers/magic_link_mailer.rb` | マジックリンクメールの送信処理 |

**主要処理フロー**:
- **2-7行目**: sign_in_instructionsメソッドでコードを含むメール送信

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

```
SignupsController#create
    │
    ├─ Signup#valid?(:identity_creation)
    │      └─ email_addressバリデーション
    │
    └─ Signup#create_identity
           ├─ Identity.find_or_create_by!(email_address:)
           └─ Identity#send_magic_link(for: :sign_up)
                  ├─ MagicLink.create!
                  │      ├─ generate_code (6桁コード生成)
                  │      └─ set_expiration (15分後)
                  └─ MagicLinkMailer.sign_in_instructions.deliver_later

Sessions::MagicLinksController#create
    │
    ├─ MagicLink.consume(code)
    │      ├─ active.find_by(code:)
    │      └─ destroy (消費)
    │
    └─ start_new_session_for(identity)
           └─ Session.create!

Signups::CompletionsController#create
    │
    └─ Signup#complete
           ├─ valid?(:completion)
           └─ create_account
                  ├─ Account.create_with_owner
                  │      ├─ Account.create!
                  │      ├─ User.create!(role: :system)
                  │      └─ User.create!(role: :owner)
                  └─ setup_customer_template
```

### データフロー図

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

email_address ─────▶ SignupsController ─────────────▶ Identity（DB）
                           │
                           └──▶ Signup#create_identity ──▶ MagicLink（DB）
                                       │
                                       └──▶ MagicLinkMailer ──▶ Email（外部）

code ─────────────▶ MagicLinksController ───────────▶ Session（DB）
                           │                               │
                           └──▶ MagicLink.consume ─────────▶ Cookie（セッショントークン）

full_name ─────────▶ CompletionsController ─────────▶ Account（DB）
                           │                               │
                           └──▶ Signup#complete ───────────▶ User（DB）
                                                            │
                                                            └──▶ JoinCode（DB）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| signups_controller.rb | `app/controllers/signups_controller.rb` | コントローラー | サインアップ開始処理 |
| completions_controller.rb | `app/controllers/signups/completions_controller.rb` | コントローラー | サインアップ完了処理 |
| magic_links_controller.rb | `app/controllers/sessions/magic_links_controller.rb` | コントローラー | マジックリンク検証 |
| signup.rb | `app/models/signup.rb` | モデル（フォームオブジェクト） | サインアップロジック |
| identity.rb | `app/models/identity.rb` | モデル | ユーザー認証基盤 |
| magic_link.rb | `app/models/magic_link.rb` | モデル | マジックリンク管理 |
| account.rb | `app/models/account.rb` | モデル | アカウント管理 |
| user.rb | `app/models/user.rb` | モデル | ユーザー管理 |
| session.rb | `app/models/session.rb` | モデル | セッション管理 |
| authentication.rb | `app/controllers/concerns/authentication.rb` | コンサーン | 認証処理 |
| magic_link_mailer.rb | `app/mailers/magic_link_mailer.rb` | メーラー | マジックリンクメール送信 |
| new.html.erb | `app/views/signups/new.html.erb` | ビュー | サインアップ画面 |
| new.html.erb | `app/views/signups/completions/new.html.erb` | ビュー | サインアップ完了画面 |
| show.html.erb | `app/views/sessions/magic_links/show.html.erb` | ビュー | マジックリンク入力画面 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義 |
