# 機能設計書 25-ユーザー認証

## 概要

本ドキュメントは、Liam DBシステムにおけるユーザー認証機能の設計を記述するものである。本機能は、Supabase Authを使用してユーザーの認証を管理し、セキュアなアクセス制御を実現する。

### 本機能の処理概要

ユーザー認証機能は、GitHub OAuthおよびEmail/Password認証の2つの認証方式を提供する。Next.jsのMiddlewareを使用して全リクエストのセッション検証を行い、未認証ユーザーをログインページにリダイレクトする。

**業務上の目的・背景**：Liam DBはチーム向けのデータベース設計ツールであり、適切なアクセス制御が必要となる。ユーザー認証により、ユーザー固有のデザインセッション、プロジェクト、組織へのアクセスを制限し、データの機密性を確保する。

**機能の利用シーン**：ユーザーがLiam DBにアクセスする際、ログインページでGitHubアカウントまたはメールアドレス/パスワードを使用して認証を行う。認証成功後、元々アクセスしようとしていたページにリダイレクトされる。

**主要な処理内容**：
1. GitHub OAuth認証（signInWithOAuth）
2. Email/Password認証（signInWithPassword）
3. Middleware によるセッション検証
4. 認証コールバック処理とトークン管理
5. ログアウト処理（セッション・Cookie の削除）
6. 初回ログイン時の組織自動作成

**関連システム・外部連携**：Supabase Auth、GitHub OAuth

**権限による制御**：認証済みユーザーのみがアプリケーションの保護されたルートにアクセス可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | ログイン画面 | 主画面 | GitHub OAuth認証、Email/Password認証 |
| 2 | ホーム画面 | 関連画面 | 認証後のリダイレクト先 |

## 機能種別

認証・セキュリティ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| email | string | Yes（Email認証の場合） | メールアドレス | valibot email() |
| password | string | Yes（Email認証の場合） | パスワード | - |
| returnTo | string | No | 認証後のリダイレクト先 | RelativeReturnPathSchema |

### 入力データソース

- ログインフォーム（EmailForm、SignInGithubButton）
- OAuth コールバックのクエリパラメータ
- Cookie（returnTo）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| session | Session | Supabase セッション情報 |
| user | User | 認証済みユーザー情報 |
| provider_token | string | GitHub アクセストークン（GitHub 認証の場合） |
| provider_refresh_token | string | GitHub リフレッシュトークン（GitHub 認証の場合） |

### 出力先

- Supabase セッション Cookie
- HttpOnly Cookie（GitHub トークン）
- 組織ID Cookie（organizationId）

## 処理フロー

### 処理シーケンス

```
1. ログインページ表示
   └─ supabase.auth.getUser() で既存セッション確認
   └─ ログイン済みの場合は returnTo にリダイレクト
2. GitHub OAuth 認証
   └─ loginByGithub() で OAuth フロー開始
   └─ GitHub 認証画面へリダイレクト
   └─ /auth/callback/github でコールバック処理
   └─ exchangeCodeForSession() でセッション取得
   └─ ensureUserHasOrganization() で組織確認・作成
   └─ GitHub トークンを Cookie に保存
3. Email/Password 認証
   └─ loginByEmail() で認証
   └─ signInWithPassword() でセッション取得
   └─ ensureUserHasOrganization() で組織確認・作成
4. Middleware セッション検証
   └─ 全リクエストで updateSession() を実行
   └─ supabase.auth.getUser() でユーザー確認
   └─ 未認証の場合は /login にリダイレクト
5. ログアウト
   └─ POST /api/logout
   └─ supabase.auth.signOut({ scope: 'global' })
   └─ Cookie クリア
```

### フローチャート

```mermaid
flowchart TD
    A[アクセス] --> B{Middleware: 認証確認}
    B -->|認証済み| C[リクエスト続行]
    B -->|未認証| D{パブリックルート?}
    D -->|Yes| C
    D -->|No| E[/login にリダイレクト]
    E --> F[ログインページ]
    F --> G{認証方式選択}
    G -->|GitHub| H[GitHub OAuth]
    G -->|Email| I[Email/Password]
    H --> J[GitHub 認証画面]
    J --> K[/auth/callback/github]
    K --> L{認証成功?}
    I --> L
    L -->|Yes| M[ensureUserHasOrganization]
    L -->|No| N[エラーページ]
    M --> O[リダイレクト先へ移動]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-25-01 | パス検証 | returnTo は相対パス（/で始まる）のみ許可 | リダイレクト時 |
| BR-25-02 | 組織自動作成 | 初回ログイン時に所属組織がない場合、自動作成 | 認証成功時 |
| BR-25-03 | トークン有効期限 | GitHub アクセストークン: 8時間、リフレッシュトークン: 6ヶ月 | OAuth 時 |
| BR-25-04 | グローバルログアウト | ログアウト時は全デバイスからサインアウト | ログアウト時 |

### 計算ロジック

- GitHub アクセストークン有効期限: `Date.now() + 8 * 60 * 60 * 1000`
- GitHub リフレッシュトークン有効期限: `Date.now() + 6 * 30 * 24 * 60 * 60 * 1000`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ensureUserHasOrganization | organization_members | SELECT | ユーザーの所属組織確認 |
| ensureUserHasOrganization | organizations | INSERT | 組織自動作成（該当時） |
| ensureUserHasOrganization | organization_members | INSERT | メンバー追加（該当時） |

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

#### organization_members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | organization_id | user_id = 認証ユーザーID | 所属確認 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| oauth_init_failed | OAuth初期化エラー | signInWithOAuth 失敗 | /error にリダイレクト |
| auth_failed | 認証エラー | signInWithPassword 失敗 | /error にリダイレクト |
| callback_failed | コールバックエラー | exchangeCodeForSession 失敗 | /error にリダイレクト |
| logout_failed | ログアウトエラー | signOut 失敗 | JSON エラーレスポンス |

### リトライ仕様

- 認証処理自体にはリトライなし
- ユーザーは再度ログインを試行可能

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

- ensureUserHasOrganization: 組織作成とメンバー追加は Supabase のトランザクションで実行

## パフォーマンス要件

- Middleware 処理: 各リクエストで supabase.auth.getUser() を実行
- セッション更新: Cookie の自動更新により再認証を回避

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

- リダイレクトパスの検証（オープンリダイレクト防止）
- HttpOnly Cookie による GitHub トークン保管
- Supabase Auth による PKCE フロー
- グローバルログアウトによる全セッション無効化

## 備考

Supabase Auth の Middleware 実装ガイドラインに従い、createServerClient と getUser() の間にコードを追加しないこと。これにより予期しないログアウトを防止する。

---

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

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

### 推奨読解順序

#### Step 1: ルートプレフィックスと認証対象を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | constants.ts | `frontend/apps/app/libs/routes/constants.ts` | ROUTE_PREFIXES の定義（PUBLIC, LOGIN, AUTH, ERD） |

**読解のコツ**: どのパスが認証対象外かを把握する。

#### Step 2: Middleware のセッション検証を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | middleware.ts | `frontend/apps/app/middleware.ts` | updateSession 関数とルーティング |

**主要処理フロー**:
- **6-94行目**: updateSession でセッション検証
- **22-43行目**: createServerClient で Supabase クライアント生成
- **51-53行目**: supabase.auth.getUser() でユーザー取得
- **55-78行目**: 未認証時の /login リダイレクト処理
- **69-75行目**: returnTo Cookie の設定

#### Step 3: ログインページと認証フローを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | LoginPage.tsx | `frontend/apps/app/components/LoginPage/LoginPage.tsx` | ログインページ構造 |
| 3-2 | SignInGithubButton.tsx | `frontend/apps/app/components/LoginPage/SignInGithubButton.tsx` | GitHub OAuth ボタン |
| 3-3 | EmailForm.tsx | `frontend/apps/app/components/LoginPage/EmailForm.tsx` | Email 認証フォーム |

**主要処理フロー**:
- **10-27行目（LoginPage.tsx）**: セッション確認とリダイレクト
- **17行目（SignInGithubButton.tsx）**: formAction で loginByGithub を呼び出し
- **64行目（EmailForm.tsx）**: formAction で loginByEmail を呼び出し

#### Step 4: 認証サービスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | loginByGithub.ts | `frontend/apps/app/components/LoginPage/services/loginByGithub.ts` | GitHub OAuth |
| 4-2 | loginByEmail.ts | `frontend/apps/app/components/LoginPage/services/loginByEmail.ts` | Email 認証 |
| 4-3 | validateReturnPath.ts | `frontend/apps/app/components/LoginPage/services/validateReturnPath.ts` | パス検証 |

**主要処理フロー**:
- **44-82行目（loginByGithub.ts）**: GitHub OAuth フロー開始
- **65-68行目**: signInWithOAuth 呼び出し
- **26-75行目（loginByEmail.ts）**: Email/Password 認証
- **60行目**: signInWithPassword 呼び出し
- **7-14行目（validateReturnPath.ts）**: RelativeReturnPathSchema で検証

#### Step 5: OAuth コールバックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | route.ts | `frontend/apps/app/app/auth/callback/[provider]/route.ts` | OAuth コールバック |
| 5-2 | ensureUserHasOrganization.ts | `frontend/apps/app/components/LoginPage/services/ensureUserHasOrganization.ts` | 組織確認・作成 |

**主要処理フロー**:
- **12-87行目（route.ts）**: GET ハンドラ
- **20行目**: exchangeCodeForSession でセッション取得
- **22行目**: ensureUserHasOrganization 呼び出し
- **37-49行目**: GitHub トークンの Cookie 保存
- **9-32行目（ensureUserHasOrganization.ts）**: 組織確認と自動作成

#### Step 6: ログアウト処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | route.ts | `frontend/apps/app/app/api/logout/route.ts` | ログアウト API |

**主要処理フロー**:
- **8-44行目**: POST ハンドラ
- **9-10行目**: Cookie クリア
- **11-14行目**: supabase.auth.signOut({ scope: 'global' })

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

```
LoginPage
    │
    ├─ SignInGithubButton
    │      └─ loginByGithub (Server Action)
    │             ├─ sanitizeReturnPath
    │             ├─ getAuthCallbackUrl
    │             └─ supabase.auth.signInWithOAuth
    │                    │
    │                    └─ GitHub OAuth
    │                           │
    │                           └─ /auth/callback/github (route.ts)
    │                                  ├─ exchangeCodeForSession
    │                                  ├─ ensureUserHasOrganization
    │                                  │      ├─ supabase.auth.getUser
    │                                  │      └─ createOrganization
    │                                  ├─ writeAccessToken
    │                                  └─ writeRefreshToken
    │
    └─ EmailForm
           └─ loginByEmail (Server Action)
                  ├─ v.safeParse (loginFormSchema)
                  ├─ supabase.auth.signInWithPassword
                  └─ ensureUserHasOrganization

middleware.ts
    │
    └─ updateSession
           ├─ createServerClient
           ├─ supabase.auth.getUser
           └─ redirect /login (未認証時)

/api/logout (route.ts)
    │
    ├─ clearOrganizationIdCookie
    ├─ clearTokens
    └─ supabase.auth.signOut
```

### データフロー図

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

Email/Password ──────────▶ signInWithPassword ─────────▶ Session
       │                        │
       │                        ▼
       │                 ensureUserHasOrganization
       │                        │
       └───────────────────────▶ redirect(returnTo)


GitHub OAuth ────────────▶ signInWithOAuth ────────────▶ GitHub 認証画面
       │                        │
       │                        ▼
       │                 /auth/callback/github
       │                        │
       │                        ├─ exchangeCodeForSession
       │                        │
       │                        ├─ ensureUserHasOrganization
       │                        │
       │                        ├─ writeAccessToken (Cookie)
       │                        │
       │                        └─ writeRefreshToken (Cookie)
       │                        │
       └───────────────────────▶ redirect(returnTo)


[Middleware]
Request ─────────────────▶ createServerClient
       │                        │
       │                        ├─ getUser
       │                        │
       │                        ├─ (未認証) returnTo Cookie 設定
       │                        │
       │                        └─ redirect /login
       │
       └───────────────────────▶ NextResponse
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| middleware.ts | `frontend/apps/app/middleware.ts` | ソース | セッション検証 |
| LoginPage.tsx | `frontend/apps/app/components/LoginPage/LoginPage.tsx` | ソース | ログインページ |
| SignInGithubButton.tsx | `frontend/apps/app/components/LoginPage/SignInGithubButton.tsx` | ソース | GitHub OAuth ボタン |
| EmailForm.tsx | `frontend/apps/app/components/LoginPage/EmailForm.tsx` | ソース | Email 認証フォーム |
| loginByGithub.ts | `frontend/apps/app/components/LoginPage/services/loginByGithub.ts` | ソース | GitHub OAuth 処理 |
| loginByEmail.ts | `frontend/apps/app/components/LoginPage/services/loginByEmail.ts` | ソース | Email 認証処理 |
| validateReturnPath.ts | `frontend/apps/app/components/LoginPage/services/validateReturnPath.ts` | ソース | パス検証 |
| ensureUserHasOrganization.ts | `frontend/apps/app/components/LoginPage/services/ensureUserHasOrganization.ts` | ソース | 組織確認・作成 |
| route.ts | `frontend/apps/app/app/auth/callback/[provider]/route.ts` | ソース | OAuth コールバック |
| route.ts | `frontend/apps/app/app/api/logout/route.ts` | ソース | ログアウト API |
| route.ts | `frontend/apps/app/app/confirm/route.ts` | ソース | Email 確認 |
| constants.ts | `frontend/apps/app/libs/routes/constants.ts` | ソース | ルートプレフィックス |
| getAuthUser.ts | `frontend/apps/app/components/CommonLayout/services/getAuthUser.ts` | ソース | ユーザー取得 |
