# 画面設計書 3-サインアップ画面

## 概要

本ドキュメントは、Ghost管理画面のサインアップ画面の設計内容を記載する。

### 本画面の処理概要

サインアップ画面は、招待トークンを使用して新規スタッフユーザーを登録するための画面である。既存のスタッフから招待メールを受け取ったユーザーが、メール内のリンクからアクセスし、アカウント情報を入力して登録を完了する。

**業務上の目的・背景**：Ghostでは、セキュリティの観点からオープンなユーザー登録は行わず、既存のスタッフユーザーからの招待制を採用している。これにより、管理者が明示的に許可したユーザーのみがスタッフとして参加でき、不正なアカウント作成を防止する。招待時に設定されたロール（Administrator、Editor、Author、Contributor）が新規ユーザーに自動的に割り当てられる。

**画面へのアクセス方法**：招待メール内のリンク（`/ghost/signup/{token}`）をクリックしてアクセスする。トークンはBase64エンコードされており、有効期限と招待されたメールアドレス情報を含む。

**主要な操作・処理内容**：
1. フルネーム入力 - 表示名として使用される名前を入力
2. メールアドレス入力 - 招待されたメールアドレスを入力（確認のため）
3. パスワード入力 - 10文字以上の安全なパスワードを設定
4. アカウント作成ボタン押下 - 招待を受諾してユーザーアカウントを作成、自動ログイン

**画面遷移**：
- 遷移元：招待メール内のリンク
- 遷移先：
  - 登録成功時：ホーム画面（自動ログイン）
  - 2FA必要時：サインイン確認画面
  - 無効なトークン時：サインイン画面

**権限による表示制御**：本画面は未認証ユーザー専用。既にログイン済みの場合、警告メッセージが表示される。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 43 | 招待機能 | 主機能 | 招待トークンを使用した新規スタッフユーザーの登録処理 |
| 40 | スタッフ管理 | 補助機能 | 新規ユーザーの作成とロール割り当て |

## 画面種別

登録

## URL/ルーティング

- **URL**: `/ghost/signup/:token`
- **Emberルート名**: `signup`
- **ルートファイル**: `ghost/admin/app/routes/signup.js`

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 入力/出力 | バリデーション | 備考 |
|--------|--------|-----|------|----------|---------------|------|
| フルネーム | name | string | 必須 | 入力 | 1文字以上 | プレースホルダー: "Jamie Larson" |
| メールアドレス | email | string | 必須 | 入力 | メール形式チェック | プレースホルダー: "jamie@example.com" |
| パスワード | password | string | 必須 | 入力 | 10文字以上、複雑性チェック | プレースホルダー: "At least 10 characters" |
| 招待トークン | token | string | 必須 | URL | Base64形式、有効性チェック | URLパラメータから取得 |

## 表示項目

| 項目名 | 表示条件 | データソース | 備考 |
|--------|----------|--------------|------|
| サイトアイコン | 常時 | config.siteIcon | スタイル属性で表示 |
| ヘッダータイトル | 常時 | 固定値 | "Create your account." |
| フローエラー | エラー発生時 | flowErrors | 画面下部に赤字表示 |
| 項目別エラー | バリデーションエラー時 | signupDetails.errors | 各入力項目下に表示 |

## イベント仕様

### 1-アカウント作成ボタン押下

**トリガー**: 「Create Account →」ボタンクリックまたはEnterキー押下

**処理フロー**:
1. `signupTask`が実行される
2. クライアント側バリデーション実行（名前、メール、パスワード）
3. バリデーション成功時、`_completeInvitation`を実行
4. APIエンドポイント`POST /ghost/api/admin/authentication/invitation`に招待受諾リクエスト送信
5. バックエンドで招待トークン検証、ユーザー作成、ロール割り当て
6. 招待受諾成功後、`_authenticateWithPassword`で自動ログイン
7. ログイン成功時：ホーム画面へリダイレクト
8. 2FA必要時：サインイン確認画面へ遷移
9. 失敗時：エラーメッセージを表示

**遷移先**:
- 成功時: `/ghost/` (ホーム画面)
- 2FA要求時: `/ghost/signin-verify`

### 2-入力欄のフォーカスアウト

**トリガー**: 各入力欄からフォーカスが外れた時

**処理フロー**:
1. `validate(property)`アクションが実行される
2. 該当プロパティのバリデーション実行
3. エラーがあれば入力欄下にエラーメッセージ表示

### 3-フルネーム入力欄のフォーカスアウト（追加処理）

**トリガー**: フルネーム入力欄からフォーカスが外れた時

**処理フロー**:
1. `trimSignupProperty("name")`で前後の空白を削除
2. その後にバリデーション実行

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| アカウント作成 | users | INSERT | 新規ユーザーレコードの作成 |
| アカウント作成 | roles_users | INSERT | ユーザーとロールの紐付け |
| アカウント作成 | invites | DELETE | 使用済み招待レコードの削除 |
| 自動ログイン | users | UPDATE | last_seen カラムを更新 |
| 自動ログイン | sessions | INSERT | 新規セッションレコードの作成 |

### テーブル別更新項目詳細

#### users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name | フォーム入力値 | 表示名 |
| INSERT | email | フォーム入力値 | ログイン用メールアドレス |
| INSERT | password | フォーム入力値（ハッシュ化） | bcryptでハッシュ化 |
| INSERT | status | 'active' | 有効状態 |
| UPDATE | last_seen | 現在日時 | 自動ログイン時に更新 |

#### roles_users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | user_id | 新規作成ユーザーID | - |
| INSERT | role_id | 招待時に指定されたロールID | invites.role_idから取得 |

#### invites

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | - | token = 招待トークン | 使用済み招待の削除 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| M001 | エラー | "Please enter a name." | 名前未入力 |
| M002 | エラー | "Please enter an email." | メールアドレス未入力 |
| M003 | エラー | "Invalid Email." | メールアドレス形式不正 |
| M004 | エラー | "Password must be at least 10 characters long." | パスワード10文字未満 |
| M005 | エラー | "Sorry, you cannot use an insecure password." | 安全でないパスワード（禁止リスト、同一文字過多など） |
| M006 | エラー | "Please fill out the form to complete your signup" | バリデーションエラー全般 |
| M007 | エラー | "Invalid token." | 招待トークン形式不正 |
| M008 | 警告 | "The invitation does not exist or is no longer valid." | 招待が存在しないまたは無効 |
| M009 | 警告 | "You need to sign out to register as a new user." | 既にログイン済み |
| M010 | エラー | "Invite not found." | 招待トークンがDB上に存在しない |
| M011 | エラー | "Invite is expired." | 招待の有効期限切れ |
| M012 | エラー | "Could not create an account, email is already in use." | メールアドレス既存 |

## 例外処理

| 例外ケース | 対応処理 | 表示内容 |
|-----------|----------|----------|
| トークン形式不正 | サインイン画面へリダイレクト | "Invalid token." |
| 招待無効・期限切れ | サインイン画面へリダイレクト | "The invitation does not exist or is no longer valid." |
| 既にログイン済み | 警告表示後、通常の未認証チェック | "You need to sign out to register as a new user." |
| メールアドレス重複 | エラー表示 | "Could not create an account, email is already in use." |
| 招待レコード不在 | エラー表示 | "Invite not found." |
| 招待有効期限切れ | エラー表示 | "Invite is expired." |
| パスワード不正 | エラー表示 | パスワード関連エラーメッセージ |
| APIバージョン不一致 | 通知表示 | バージョン不一致エラー内容 |

## 備考

- 招待トークンはBase64エンコードされており、デコードするとタイムスタンプとメールアドレスが取得できる
- パスワードバリデーションは以下の条件をチェック：
  - 10文字以上
  - 禁止パスワードリスト（'1234567890', 'qwertyuiop'など）に該当しない
  - 'ghost', 'password', 'passw0rd'を含まない
  - ユーザーのメールアドレスと同一でない
  - ブログタイトル、ブログURLと同一でない
  - 同一文字が50%以上を占めない
- 登録完了後は自動的にログイン処理が実行される
- 招待レコードは使用後に削除される（再利用不可）

---

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

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

### 推奨読解順序

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

サインアップに使用されるデータモデルとバリデーションルールを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | signup.js (Route) | `ghost/admin/app/routes/signup.js` | SignupDetailsクラス（11-20行目）のデータ構造、ValidationEngine継承 |
| 1-2 | new-user.js | `ghost/admin/app/validators/new-user.js` | 名前、メール、パスワードのバリデーションルール |
| 1-3 | password.js | `ghost/admin/app/validators/mixins/password.js` | パスワード複雑性チェックの詳細ルール |

**読解のコツ**: `@classic`デコレータはEmberClassicのクラス定義を示す。ValidationEngineミックスインにより`validate()`メソッドが提供される。

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

テンプレート、ルート、コントローラーの関係を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | signup.hbs | `ghost/admin/app/templates/signup.hbs` | フォーム構造、イベントバインディング、エラー表示 |
| 2-2 | signup.js (Route) | `ghost/admin/app/routes/signup.js` | model()でのトークン検証、beforeModel()での認証状態チェック |

**主要処理フロー**:
1. **30-36行目**: beforeModelで認証状態チェック、ログイン済みなら警告表示
2. **38-84行目**: modelでトークンをデコードし、招待有効性をAPIで確認
3. **86-94行目**: deactivateで機密情報クリア

#### Step 3: コントローラーのタスク処理を理解する

サインアップ処理の実装を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | signup.js (Controller) | `ghost/admin/app/controllers/signup.js` | signupTask（49-89行目）、_completeInvitation（91-106行目）、_authenticateWithPassword（108-113行目）の処理フロー |

**主要処理フロー**:
- **49-89行目**: バリデーション → 招待受諾 → 自動ログインの一連の流れ
- **91-106行目**: POST /authentication/invitationで招待受諾
- **108-113行目**: 作成したアカウントで自動ログイン
- **63-66行目**: 2FA要求時のサインイン確認画面への遷移

#### Step 4: バックエンドAPI処理を理解する

サーバーサイドの招待受諾処理を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | authentication.js | `ghost/core/core/server/api/endpoints/authentication.js` | acceptInvitation（174-186行目）、isInvitation（188-200行目）のエンドポイント定義 |
| 4-2 | accept.js | `ghost/core/core/server/services/invitations/accept.js` | 招待受諾処理の詳細実装 |

**主要処理フロー**:
- **accept.js 21行目**: invitesテーブルからトークンで招待レコードを検索
- **accept.js 27-29行目**: 有効期限チェック
- **accept.js 31-38行目**: メールアドレス重複チェック
- **accept.js 40-45行目**: User.addでユーザー作成（ロール割り当て含む）
- **accept.js 47行目**: 招待レコード削除

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

```
signup.hbs (テンプレート)
    │
    ├─ SignupRoute.model (初期化)
    │      ├─ トークンデコード（atob）
    │      └─ GET /authentication/invitation (招待有効性確認)
    │
    └─ SignupController.signupTask
           ├─ SignupDetails.validate
           │      ├─ NewUserValidator.name
           │      ├─ NewUserValidator.email
           │      └─ NewUserValidator.password
           │             └─ PasswordValidatorMixin.passwordValidation
           │
           ├─ _completeInvitation
           │      └─ POST /authentication/invitation
           │             └─ AuthenticationController.acceptInvitation
           │                    └─ invitations.accept
           │                           ├─ Invite.findOne
           │                           ├─ User.findOne (重複チェック)
           │                           ├─ User.add
           │                           └─ invite.destroy
           │
           └─ _authenticateWithPassword
                  └─ session.authenticate('authenticator:cookie')
                         └─ POST /session
```

### データフロー図

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

招待メール ──────▶ トークンデコード ──────▶ メールアドレス抽出
(token)                   │
                          ▼
                    GET /invitation ──────▶ 招待有効性確認
                          │
                          ▼
name ─────────────┐
email ────────────┼──▶ signupTask ───────────────────────────▶ ホーム画面へ遷移
password ─────────┘       │                                     または
                          │                                     エラー表示
                          ▼
                    POST /invitation
                          │
                          ▼
                    ┌─────┴─────┐
                    │           │
                 users       invites
                INSERT       DELETE
                    │
                    ▼
                 roles_users
                  INSERT
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| signup.hbs | `ghost/admin/app/templates/signup.hbs` | テンプレート | サインアップフォームのUI定義 |
| signup.js | `ghost/admin/app/routes/signup.js` | ルート | ルーティング、トークン検証、モデル初期化 |
| signup.js | `ghost/admin/app/controllers/signup.js` | コントローラー | フォーム処理とタスク定義 |
| signup.js | `ghost/admin/app/validators/signup.js` | バリデータ | NewUserValidatorを継承 |
| new-user.js | `ghost/admin/app/validators/new-user.js` | バリデータ | 名前、メール、パスワードのバリデーション |
| password.js | `ghost/admin/app/validators/mixins/password.js` | ミックスイン | パスワード複雑性バリデーション |
| authentication.js | `ghost/core/core/server/api/endpoints/authentication.js` | API | 招待関連エンドポイント |
| accept.js | `ghost/core/core/server/services/invitations/accept.js` | サービス | 招待受諾処理 |
| user.js | `ghost/core/core/server/models/user.js` | モデル | ユーザーモデル |
| invite.js | `ghost/core/core/server/models/invite.js` | モデル | 招待モデル |
| cookie.js | `ghost/admin/app/authenticators/cookie.js` | 認証 | 自動ログイン処理 |
| unauthenticated.js | `ghost/admin/app/routes/unauthenticated.js` | ルート（親） | 未認証ルートの基底クラス |
